001/* 002 * $Id: AcroFields.java 4784 2011-03-15 08:33:00Z blowagie $ 003 * 004 * This file is part of the iText (R) project. 005 * Copyright (c) 1998-2011 1T3XT BVBA 006 * Authors: Bruno Lowagie, Paulo Soares, et al. 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU Affero General Public License version 3 010 * as published by the Free Software Foundation with the addition of the 011 * following permission added to Section 15 as permitted in Section 7(a): 012 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY 1T3XT, 013 * 1T3XT DISCLAIMS THE WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. 014 * 015 * This program is distributed in the hope that it will be useful, but 016 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 017 * or FITNESS FOR A PARTICULAR PURPOSE. 018 * See the GNU Affero General Public License for more details. 019 * You should have received a copy of the GNU Affero General Public License 020 * along with this program; if not, see http://www.gnu.org/licenses or write to 021 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 022 * Boston, MA, 02110-1301 USA, or download the license from the following URL: 023 * http://itextpdf.com/terms-of-use/ 024 * 025 * The interactive user interfaces in modified source and object code versions 026 * of this program must display Appropriate Legal Notices, as required under 027 * Section 5 of the GNU Affero General Public License. 028 * 029 * In accordance with Section 7(b) of the GNU Affero General Public License, 030 * a covered work must retain the producer line in every PDF that is created 031 * or manipulated using iText. 032 * 033 * You can be released from the requirements of the license by purchasing 034 * a commercial license. Buying such a license is mandatory as soon as you 035 * develop commercial activities involving the iText software without 036 * disclosing the source code of your own applications. 037 * These activities include: offering paid services to customers as an ASP, 038 * serving PDFs on the fly in a web application, shipping iText with a closed 039 * source product. 040 * 041 * For more information, please contact iText Software Corp. at this 042 * address: sales@itextpdf.com 043 */ 044package com.itextpdf.text.pdf; 045 046import java.io.IOException; 047import java.io.InputStream; 048import java.util.ArrayList; 049import java.util.Collections; 050import java.util.Comparator; 051import java.util.HashMap; 052import java.util.HashSet; 053import java.util.Iterator; 054import java.util.List; 055import java.util.Map; 056 057import org.w3c.dom.Node; 058 059import com.itextpdf.text.BaseColor; 060import com.itextpdf.text.DocumentException; 061import com.itextpdf.text.Element; 062import com.itextpdf.text.ExceptionConverter; 063import com.itextpdf.text.Image; 064import com.itextpdf.text.Rectangle; 065import com.itextpdf.text.error_messages.MessageLocalization; 066import com.itextpdf.text.pdf.PRTokeniser.TokenType; 067import com.itextpdf.text.pdf.codec.Base64; 068 069/** 070 * Query and change fields in existing documents either by method 071 * calls or by FDF merging. 072 * 073 * @author Paulo Soares 074 */ 075public class AcroFields { 076 077 PdfReader reader; 078 PdfWriter writer; 079 Map<String, Item> fields; 080 private int topFirst; 081 private HashMap<String, int[]> sigNames; 082 private boolean append; 083 public static final int DA_FONT = 0; 084 public static final int DA_SIZE = 1; 085 public static final int DA_COLOR = 2; 086 private HashMap<Integer, BaseFont> extensionFonts = new HashMap<Integer, BaseFont>(); 087 private XfaForm xfa; 088 089 /** 090 * A field type invalid or not found. 091 */ 092 public static final int FIELD_TYPE_NONE = 0; 093 094 /** 095 * A field type. 096 */ 097 public static final int FIELD_TYPE_PUSHBUTTON = 1; 098 099 /** 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<String, BaseFont> localFonts = new HashMap<String, BaseFont>(); 135 136 private float extraMarginLeft; 137 private float extraMarginTop; 138 private ArrayList<BaseFont> substitutionFonts; 139 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 155 void fill() { 156 fields = new HashMap<String, Item>(); 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 = 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 */ 264 public String[] getAppearanceStates(String fieldName) { 265 Item fd = fields.get(fieldName); 266 if (fd == null) 267 return null; 268 HashSet<String> names = new HashSet<String>(); 269 PdfDictionary vals = fd.getValue(0); 270 PdfString stringOpt = vals.getAsString( PdfName.OPT ); 271 if (stringOpt != null) { 272 names.add(stringOpt.toUnicodeString()); 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.add(valStr.toUnicodeString()); 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 (Object element : dic.getKeys()) { 293 String name = PdfName.decodeName(((PdfName)element).toString()); 294 names.add(name); 295 } 296 } 297 String out[] = new String[names.size()]; 298 return names.toArray(out); 299 } 300 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 */ 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 */ 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 */ 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(MessageLocalization.getComposedMessage("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 = 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 */ 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 */ 457 public void exportAsFdf(FdfWriter writer) { 458 for (Map.Entry<String, Item> entry : fields.entrySet()) { 459 Item item = entry.getValue(); 460 String name = entry.getKey(); 461 PdfObject v = item.getMerged(0).get(PdfName.V); 462 if (v == null) 463 continue; 464 String value = getField(name); 465 if (lastWasString) 466 writer.setFieldAsString(name, value); 467 else 468 writer.setFieldAsName(name, value); 469 } 470 } 471 472 /** 473 * Renames a field. Only the last part of the name can be renamed. For example, 474 * if the original field is "ab.cd.ef" only the "ef" part can be renamed. 475 * 476 * @param oldName the old field name 477 * @param newName the new field name 478 * @return <CODE>true</CODE> if the renaming was successful, <CODE>false</CODE> 479 * otherwise 480 */ 481 public boolean renameField(String oldName, String newName) { 482 int idx1 = oldName.lastIndexOf('.') + 1; 483 int idx2 = newName.lastIndexOf('.') + 1; 484 if (idx1 != idx2) 485 return false; 486 if (!oldName.substring(0, idx1).equals(newName.substring(0, idx2))) 487 return false; 488 if (fields.containsKey(newName)) 489 return false; 490 Item item = fields.get(oldName); 491 if (item == null) 492 return false; 493 newName = newName.substring(idx2); 494 PdfString ss = new PdfString(newName, PdfObject.TEXT_UNICODE); 495 496 item.writeToAll( PdfName.T, ss, Item.WRITE_VALUE | Item.WRITE_MERGED); 497 item.markUsed( this, Item.WRITE_VALUE ); 498 499 fields.remove(oldName); 500 fields.put(newName, item); 501 502 return true; 503 } 504 505 public static Object[] splitDAelements(String da) { 506 try { 507 PRTokeniser tk = new PRTokeniser(PdfEncodings.convertToBytes(da, null)); 508 ArrayList<String> stack = new ArrayList<String>(); 509 Object ret[] = new Object[3]; 510 while (tk.nextToken()) { 511 if (tk.getTokenType() == TokenType.COMMENT) 512 continue; 513 if (tk.getTokenType() == TokenType.OTHER) { 514 String operator = tk.getStringValue(); 515 if (operator.equals("Tf")) { 516 if (stack.size() >= 2) { 517 ret[DA_FONT] = stack.get(stack.size() - 2); 518 ret[DA_SIZE] = new Float(stack.get(stack.size() - 1)); 519 } 520 } 521 else if (operator.equals("g")) { 522 if (stack.size() >= 1) { 523 float gray = new Float(stack.get(stack.size() - 1)).floatValue(); 524 if (gray != 0) 525 ret[DA_COLOR] = new GrayColor(gray); 526 } 527 } 528 else if (operator.equals("rg")) { 529 if (stack.size() >= 3) { 530 float red = new Float(stack.get(stack.size() - 3)).floatValue(); 531 float green = new Float(stack.get(stack.size() - 2)).floatValue(); 532 float blue = new Float(stack.get(stack.size() - 1)).floatValue(); 533 ret[DA_COLOR] = new BaseColor(red, green, blue); 534 } 535 } 536 else if (operator.equals("k")) { 537 if (stack.size() >= 4) { 538 float cyan = new Float(stack.get(stack.size() - 4)).floatValue(); 539 float magenta = new Float(stack.get(stack.size() - 3)).floatValue(); 540 float yellow = new Float(stack.get(stack.size() - 2)).floatValue(); 541 float black = new Float(stack.get(stack.size() - 1)).floatValue(); 542 ret[DA_COLOR] = new CMYKColor(cyan, magenta, yellow, black); 543 } 544 } 545 stack.clear(); 546 } 547 else 548 stack.add(tk.getStringValue()); 549 } 550 return ret; 551 } 552 catch (IOException ioe) { 553 throw new ExceptionConverter(ioe); 554 } 555 } 556 557 public void decodeGenericDictionary(PdfDictionary merged, BaseField tx) throws IOException, DocumentException { 558 int flags = 0; 559 // the text size and color 560 PdfString da = merged.getAsString(PdfName.DA); 561 if (da != null) { 562 Object dab[] = splitDAelements(da.toUnicodeString()); 563 if (dab[DA_SIZE] != null) 564 tx.setFontSize(((Float)dab[DA_SIZE]).floatValue()); 565 if (dab[DA_COLOR] != null) 566 tx.setTextColor((BaseColor)dab[DA_COLOR]); 567 if (dab[DA_FONT] != null) { 568 PdfDictionary font = merged.getAsDict(PdfName.DR); 569 if (font != null) { 570 font = font.getAsDict(PdfName.FONT); 571 if (font != null) { 572 PdfObject po = font.get(new PdfName((String)dab[DA_FONT])); 573 if (po != null && po.type() == PdfObject.INDIRECT) { 574 PRIndirectReference por = (PRIndirectReference)po; 575 BaseFont bp = new DocumentFont((PRIndirectReference)po); 576 tx.setFont(bp); 577 Integer porkey = Integer.valueOf(por.getNumber()); 578 BaseFont porf = extensionFonts.get(porkey); 579 if (porf == null) { 580 if (!extensionFonts.containsKey(porkey)) { 581 PdfDictionary fo = (PdfDictionary)PdfReader.getPdfObject(po); 582 PdfDictionary fd = fo.getAsDict(PdfName.FONTDESCRIPTOR); 583 if (fd != null) { 584 PRStream prs = (PRStream)PdfReader.getPdfObject(fd.get(PdfName.FONTFILE2)); 585 if (prs == null) 586 prs = (PRStream)PdfReader.getPdfObject(fd.get(PdfName.FONTFILE3)); 587 if (prs == null) { 588 extensionFonts.put(porkey, null); 589 } 590 else { 591 try { 592 porf = BaseFont.createFont("font.ttf", BaseFont.IDENTITY_H, true, false, PdfReader.getStreamBytes(prs), null); 593 } 594 catch (Exception e) { 595 } 596 extensionFonts.put(porkey, porf); 597 } 598 } 599 } 600 } 601 if (tx instanceof TextField) 602 ((TextField)tx).setExtensionFont(porf); 603 } 604 else { 605 BaseFont bf = localFonts.get(dab[DA_FONT]); 606 if (bf == null) { 607 String fn[] = stdFieldFontNames.get(dab[DA_FONT]); 608 if (fn != null) { 609 try { 610 String enc = "winansi"; 611 if (fn.length > 1) 612 enc = fn[1]; 613 bf = BaseFont.createFont(fn[0], enc, false); 614 tx.setFont(bf); 615 } 616 catch (Exception e) { 617 // empty 618 } 619 } 620 } 621 else 622 tx.setFont(bf); 623 } 624 } 625 } 626 } 627 } 628 //rotation, border and background color 629 PdfDictionary mk = merged.getAsDict(PdfName.MK); 630 if (mk != null) { 631 PdfArray ar = mk.getAsArray(PdfName.BC); 632 BaseColor border = getMKColor(ar); 633 tx.setBorderColor(border); 634 if (border != null) 635 tx.setBorderWidth(1); 636 ar = mk.getAsArray(PdfName.BG); 637 tx.setBackgroundColor(getMKColor(ar)); 638 PdfNumber rotation = mk.getAsNumber(PdfName.R); 639 if (rotation != null) 640 tx.setRotation(rotation.intValue()); 641 } 642 //flags 643 PdfNumber nfl = merged.getAsNumber(PdfName.F); 644 flags = 0; 645 tx.setVisibility(BaseField.VISIBLE_BUT_DOES_NOT_PRINT); 646 if (nfl != null) { 647 flags = nfl.intValue(); 648 if ((flags & PdfFormField.FLAGS_PRINT) != 0 && (flags & PdfFormField.FLAGS_HIDDEN) != 0) 649 tx.setVisibility(BaseField.HIDDEN); 650 else if ((flags & PdfFormField.FLAGS_PRINT) != 0 && (flags & PdfFormField.FLAGS_NOVIEW) != 0) 651 tx.setVisibility(BaseField.HIDDEN_BUT_PRINTABLE); 652 else if ((flags & PdfFormField.FLAGS_PRINT) != 0) 653 tx.setVisibility(BaseField.VISIBLE); 654 } 655 //multiline 656 nfl = merged.getAsNumber(PdfName.FF); 657 flags = 0; 658 if (nfl != null) 659 flags = nfl.intValue(); 660 tx.setOptions(flags); 661 if ((flags & PdfFormField.FF_COMB) != 0) { 662 PdfNumber maxLen = merged.getAsNumber(PdfName.MAXLEN); 663 int len = 0; 664 if (maxLen != null) 665 len = maxLen.intValue(); 666 tx.setMaxCharacterLength(len); 667 } 668 //alignment 669 nfl = merged.getAsNumber(PdfName.Q); 670 if (nfl != null) { 671 if (nfl.intValue() == PdfFormField.Q_CENTER) 672 tx.setAlignment(Element.ALIGN_CENTER); 673 else if (nfl.intValue() == PdfFormField.Q_RIGHT) 674 tx.setAlignment(Element.ALIGN_RIGHT); 675 } 676 //border styles 677 PdfDictionary bs = merged.getAsDict(PdfName.BS); 678 if (bs != null) { 679 PdfNumber w = bs.getAsNumber(PdfName.W); 680 if (w != null) 681 tx.setBorderWidth(w.floatValue()); 682 PdfName s = bs.getAsName(PdfName.S); 683 if (PdfName.D.equals(s)) 684 tx.setBorderStyle(PdfBorderDictionary.STYLE_DASHED); 685 else if (PdfName.B.equals(s)) 686 tx.setBorderStyle(PdfBorderDictionary.STYLE_BEVELED); 687 else if (PdfName.I.equals(s)) 688 tx.setBorderStyle(PdfBorderDictionary.STYLE_INSET); 689 else if (PdfName.U.equals(s)) 690 tx.setBorderStyle(PdfBorderDictionary.STYLE_UNDERLINE); 691 } 692 else { 693 PdfArray bd = merged.getAsArray(PdfName.BORDER); 694 if (bd != null) { 695 if (bd.size() >= 3) 696 tx.setBorderWidth(bd.getAsNumber(2).floatValue()); 697 if (bd.size() >= 4) 698 tx.setBorderStyle(PdfBorderDictionary.STYLE_DASHED); 699 } 700 } 701 } 702 703 PdfAppearance getAppearance(PdfDictionary merged, String values[], String fieldName) throws IOException, DocumentException { 704 topFirst = 0; 705 String text = values.length > 0 ? values[0] : null; 706 707 TextField tx = null; 708 if (fieldCache == null || !fieldCache.containsKey(fieldName)) { 709 tx = new TextField(writer, null, null); 710 tx.setExtraMargin(extraMarginLeft, extraMarginTop); 711 tx.setBorderWidth(0); 712 tx.setSubstitutionFonts(substitutionFonts); 713 decodeGenericDictionary(merged, tx); 714 //rect 715 PdfArray rect = merged.getAsArray(PdfName.RECT); 716 Rectangle box = PdfReader.getNormalizedRectangle(rect); 717 if (tx.getRotation() == 90 || tx.getRotation() == 270) 718 box = box.rotate(); 719 tx.setBox(box); 720 if (fieldCache != null) 721 fieldCache.put(fieldName, tx); 722 } 723 else { 724 tx = fieldCache.get(fieldName); 725 tx.setWriter(writer); 726 } 727 PdfName fieldType = merged.getAsName(PdfName.FT); 728 if (PdfName.TX.equals(fieldType)) { 729 if (values.length > 0 && values[0] != null) { 730 tx.setText(values[0]); 731 } 732 return tx.getAppearance(); 733 } 734 if (!PdfName.CH.equals(fieldType)) 735 throw new DocumentException(MessageLocalization.getComposedMessage("an.appearance.was.requested.without.a.variable.text.field")); 736 PdfArray opt = merged.getAsArray(PdfName.OPT); 737 int flags = 0; 738 PdfNumber nfl = merged.getAsNumber(PdfName.FF); 739 if (nfl != null) 740 flags = nfl.intValue(); 741 if ((flags & PdfFormField.FF_COMBO) != 0 && opt == null) { 742 tx.setText(text); 743 return tx.getAppearance(); 744 } 745 if (opt != null) { 746 String choices[] = new String[opt.size()]; 747 String choicesExp[] = new String[opt.size()]; 748 for (int k = 0; k < opt.size(); ++k) { 749 PdfObject obj = opt.getPdfObject(k); 750 if (obj.isString()) { 751 choices[k] = choicesExp[k] = ((PdfString)obj).toUnicodeString(); 752 } 753 else { 754 PdfArray a = (PdfArray) obj; 755 choicesExp[k] = a.getAsString(0).toUnicodeString(); 756 choices[k] = a.getAsString(1).toUnicodeString(); 757 } 758 } 759 if ((flags & PdfFormField.FF_COMBO) != 0) { 760 for (int k = 0; k < choices.length; ++k) { 761 if (text.equals(choicesExp[k])) { 762 text = choices[k]; 763 break; 764 } 765 } 766 tx.setText(text); 767 return tx.getAppearance(); 768 } 769 ArrayList<Integer> indexes = new ArrayList<Integer>(); 770 for (int k = 0; k < choicesExp.length; ++k) { 771 for (int j = 0; j < values.length; ++j) { 772 String val = values[j]; 773 if (val != null && val.equals(choicesExp[k])) { 774 indexes.add( Integer.valueOf( k ) ); 775 break; 776 } 777 } 778 } 779 tx.setChoices(choices); 780 tx.setChoiceExports(choicesExp); 781 tx.setChoiceSelections( indexes ); 782 } 783 PdfAppearance app = tx.getListAppearance(); 784 topFirst = tx.getTopFirst(); 785 return app; 786 } 787 788 PdfAppearance getAppearance(PdfDictionary merged, String text, String fieldName) throws IOException, DocumentException { 789 String valueArr[] = new String[1]; 790 valueArr[0] = text; 791 return getAppearance( merged, valueArr, fieldName ); 792 } 793 794 BaseColor getMKColor(PdfArray ar) { 795 if (ar == null) 796 return null; 797 switch (ar.size()) { 798 case 1: 799 return new GrayColor(ar.getAsNumber(0).floatValue()); 800 case 3: 801 return new BaseColor(ExtendedColor.normalize(ar.getAsNumber(0).floatValue()), ExtendedColor.normalize(ar.getAsNumber(1).floatValue()), ExtendedColor.normalize(ar.getAsNumber(2).floatValue())); 802 case 4: 803 return new CMYKColor(ar.getAsNumber(0).floatValue(), ar.getAsNumber(1).floatValue(), ar.getAsNumber(2).floatValue(), ar.getAsNumber(3).floatValue()); 804 default: 805 return null; 806 } 807 } 808 809 /** 810 * Retrieve the rich value for the given field 811 * @param name 812 * @return The rich value if present, or null. 813 * @since 5.0.6 814 */ 815 public String getFieldRichValue(String name) { 816 if (xfa.isXfaPresent()) { 817 return null; 818 } 819 820 Item item = fields.get(name); 821 if (item == null) { 822 return null; 823 } 824 825 PdfDictionary merged = item.getMerged(0); 826 PdfString rich = merged.getAsString(PdfName.RV); 827 828 String markup = null; 829 if (rich != null) { 830 markup = rich.toString(); 831 } 832 833 return markup; 834 } 835 /** 836 * Gets the field value. 837 * 838 * @param name the fully qualified field name 839 * @return the field value 840 */ 841 public String getField(String name) { 842 if (xfa.isXfaPresent()) { 843 name = xfa.findFieldName(name, this); 844 if (name == null) 845 return null; 846 name = XfaForm.Xml2Som.getShortName(name); 847 return XfaForm.getNodeText(xfa.findDatasetsNode(name)); 848 } 849 Item item = fields.get(name); 850 if (item == null) 851 return null; 852 lastWasString = false; 853 PdfDictionary mergedDict = item.getMerged( 0 ); 854 855 // Jose A. Rodriguez posted a fix to the mailing list (May 11, 2009) 856 // explaining that the value can also be a stream value 857 // the fix was made against an old iText version. Bruno adapted it. 858 PdfObject v = PdfReader.getPdfObject(mergedDict.get(PdfName.V)); 859 if (v == null) 860 return ""; 861 if (v instanceof PRStream) { 862 byte[] valBytes; 863 try { 864 valBytes = PdfReader.getStreamBytes((PRStream)v); 865 return new String(valBytes); 866 } catch (IOException e) { 867 throw new ExceptionConverter(e); 868 } 869 } 870 871 PdfName type = mergedDict.getAsName(PdfName.FT); 872 if (PdfName.BTN.equals(type)) { 873 PdfNumber ff = mergedDict.getAsNumber(PdfName.FF); 874 int flags = 0; 875 if (ff != null) 876 flags = ff.intValue(); 877 if ((flags & PdfFormField.FF_PUSHBUTTON) != 0) 878 return ""; 879 String value = ""; 880 if (v instanceof PdfName) 881 value = PdfName.decodeName(v.toString()); 882 else if (v instanceof PdfString) 883 value = ((PdfString)v).toUnicodeString(); 884 PdfArray opts = item.getValue(0).getAsArray(PdfName.OPT); 885 if (opts != null) { 886 int idx = 0; 887 try { 888 idx = Integer.parseInt(value); 889 PdfString ps = opts.getAsString(idx); 890 value = ps.toUnicodeString(); 891 lastWasString = true; 892 } 893 catch (Exception e) { 894 } 895 } 896 return value; 897 } 898 if (v instanceof PdfString) { 899 lastWasString = true; 900 return ((PdfString)v).toUnicodeString(); 901 } else if (v instanceof PdfName) { 902 return PdfName.decodeName(v.toString()); 903 } else 904 return ""; 905 } 906 907 /** 908 * Gets the field values of a Choice field. 909 * 910 * @param name the fully qualified field name 911 * @return the field value 912 * @since 2.1.3 913 */ 914 public String[] getListSelection(String name) { 915 String[] ret; 916 String s = getField(name); 917 if (s == null) { 918 ret = new String[]{}; 919 } 920 else { 921 ret = new String[]{ s }; 922 } 923 Item item = fields.get(name); 924 if (item == null) 925 return ret; 926 //PdfName type = (PdfName)PdfReader.getPdfObject(((PdfDictionary)item.merged.get(0)).get(PdfName.FT)); 927 //if (!PdfName.CH.equals(type)) { 928 // return ret; 929 //} 930 PdfArray values = item.getMerged(0).getAsArray(PdfName.I); 931 if (values == null) 932 return ret; 933 ret = new String[values.size()]; 934 String[] options = getListOptionExport(name); 935 PdfNumber n; 936 int idx = 0; 937 for (Iterator<PdfObject> i = values.listIterator(); i.hasNext(); ) { 938 n = (PdfNumber)i.next(); 939 ret[idx++] = options[n.intValue()]; 940 } 941 return ret; 942 } 943 944 945 /** 946 * Sets a field property. Valid property names are: 947 * <p> 948 * <ul> 949 * <li>textfont - sets the text font. The value for this entry is a <CODE>BaseFont</CODE>.<br> 950 * <li>textcolor - sets the text color. The value for this entry is a <CODE>BaseColor</CODE>.<br> 951 * <li>textsize - sets the text size. The value for this entry is a <CODE>Float</CODE>. 952 * <li>bgcolor - sets the background color. The value for this entry is a <CODE>BaseColor</CODE>. 953 * If <code>null</code> removes the background.<br> 954 * <li>bordercolor - sets the border color. The value for this entry is a <CODE>BaseColor</CODE>. 955 * If <code>null</code> removes the border.<br> 956 * </ul> 957 * 958 * @param field the field name 959 * @param name the property name 960 * @param value the property value 961 * @param inst an array of <CODE>int</CODE> indexing into <CODE>AcroField.Item.merged</CODE> elements to process. 962 * Set to <CODE>null</CODE> to process all 963 * @return <CODE>true</CODE> if the property exists, <CODE>false</CODE> otherwise 964 */ 965 public boolean setFieldProperty(String field, String name, Object value, int inst[]) { 966 if (writer == null) 967 throw new RuntimeException(MessageLocalization.getComposedMessage("this.acrofields.instance.is.read.only")); 968 try { 969 Item item = fields.get(field); 970 if (item == null) 971 return false; 972 InstHit hit = new InstHit(inst); 973 PdfDictionary merged; 974 PdfString da; 975 if (name.equalsIgnoreCase("textfont")) { 976 for (int k = 0; k < item.size(); ++k) { 977 if (hit.isHit(k)) { 978 merged = item.getMerged( k ); 979 da = merged.getAsString(PdfName.DA); 980 PdfDictionary dr = merged.getAsDict(PdfName.DR); 981 if (da != null && dr != null) { 982 Object dao[] = splitDAelements(da.toUnicodeString()); 983 PdfAppearance cb = new PdfAppearance(); 984 if (dao[DA_FONT] != null) { 985 BaseFont bf = (BaseFont)value; 986 PdfName psn = PdfAppearance.stdFieldFontNames.get(bf.getPostscriptFontName()); 987 if (psn == null) { 988 psn = new PdfName(bf.getPostscriptFontName()); 989 } 990 PdfDictionary fonts = dr.getAsDict(PdfName.FONT); 991 if (fonts == null) { 992 fonts = new PdfDictionary(); 993 dr.put(PdfName.FONT, fonts); 994 } 995 PdfIndirectReference fref = (PdfIndirectReference)fonts.get(psn); 996 PdfDictionary top = reader.getCatalog().getAsDict(PdfName.ACROFORM); 997 markUsed(top); 998 dr = top.getAsDict(PdfName.DR); 999 if (dr == null) { 1000 dr = new PdfDictionary(); 1001 top.put(PdfName.DR, dr); 1002 } 1003 markUsed(dr); 1004 PdfDictionary fontsTop = dr.getAsDict(PdfName.FONT); 1005 if (fontsTop == null) { 1006 fontsTop = new PdfDictionary(); 1007 dr.put(PdfName.FONT, fontsTop); 1008 } 1009 markUsed(fontsTop); 1010 PdfIndirectReference frefTop = (PdfIndirectReference)fontsTop.get(psn); 1011 if (frefTop != null) { 1012 if (fref == null) 1013 fonts.put(psn, frefTop); 1014 } 1015 else if (fref == null) { 1016 FontDetails fd; 1017 if (bf.getFontType() == BaseFont.FONT_TYPE_DOCUMENT) { 1018 fd = new FontDetails(null, ((DocumentFont)bf).getIndirectReference(), bf); 1019 } 1020 else { 1021 bf.setSubset(false); 1022 fd = writer.addSimple(bf); 1023 localFonts.put(psn.toString().substring(1), bf); 1024 } 1025 fontsTop.put(psn, fd.getIndirectReference()); 1026 fonts.put(psn, fd.getIndirectReference()); 1027 } 1028 ByteBuffer buf = cb.getInternalBuffer(); 1029 buf.append(psn.getBytes()).append(' ').append(((Float)dao[DA_SIZE]).floatValue()).append(" Tf "); 1030 if (dao[DA_COLOR] != null) 1031 cb.setColorFill((BaseColor)dao[DA_COLOR]); 1032 PdfString s = new PdfString(cb.toString()); 1033 item.getMerged(k).put(PdfName.DA, s); 1034 item.getWidget(k).put(PdfName.DA, s); 1035 markUsed(item.getWidget(k)); 1036 } 1037 } 1038 } 1039 } 1040 } 1041 else if (name.equalsIgnoreCase("textcolor")) { 1042 for (int k = 0; k < item.size(); ++k) { 1043 if (hit.isHit(k)) { 1044 merged = item.getMerged( k ); 1045 da = merged.getAsString(PdfName.DA); 1046 if (da != null) { 1047 Object dao[] = splitDAelements(da.toUnicodeString()); 1048 PdfAppearance cb = new PdfAppearance(); 1049 if (dao[DA_FONT] != null) { 1050 ByteBuffer buf = cb.getInternalBuffer(); 1051 buf.append(new PdfName((String)dao[DA_FONT]).getBytes()).append(' ').append(((Float)dao[DA_SIZE]).floatValue()).append(" Tf "); 1052 cb.setColorFill((BaseColor)value); 1053 PdfString s = new PdfString(cb.toString()); 1054 item.getMerged(k).put(PdfName.DA, s); 1055 item.getWidget(k).put(PdfName.DA, s); 1056 markUsed(item.getWidget(k)); 1057 } 1058 } 1059 } 1060 } 1061 } 1062 else if (name.equalsIgnoreCase("textsize")) { 1063 for (int k = 0; k < item.size(); ++k) { 1064 if (hit.isHit(k)) { 1065 merged = item.getMerged( k ); 1066 da = merged.getAsString(PdfName.DA); 1067 if (da != null) { 1068 Object dao[] = splitDAelements(da.toUnicodeString()); 1069 PdfAppearance cb = new PdfAppearance(); 1070 if (dao[DA_FONT] != null) { 1071 ByteBuffer buf = cb.getInternalBuffer(); 1072 buf.append(new PdfName((String)dao[DA_FONT]).getBytes()).append(' ').append(((Float)value).floatValue()).append(" Tf "); 1073 if (dao[DA_COLOR] != null) 1074 cb.setColorFill((BaseColor)dao[DA_COLOR]); 1075 PdfString s = new PdfString(cb.toString()); 1076 item.getMerged(k).put(PdfName.DA, s); 1077 item.getWidget(k).put(PdfName.DA, s); 1078 markUsed(item.getWidget(k)); 1079 } 1080 } 1081 } 1082 } 1083 } 1084 else if (name.equalsIgnoreCase("bgcolor") || name.equalsIgnoreCase("bordercolor")) { 1085 PdfName dname = name.equalsIgnoreCase("bgcolor") ? PdfName.BG : PdfName.BC; 1086 for (int k = 0; k < item.size(); ++k) { 1087 if (hit.isHit(k)) { 1088 merged = item.getMerged( k ); 1089 PdfDictionary mk = merged.getAsDict(PdfName.MK); 1090 if (mk == null) { 1091 if (value == null) 1092 return true; 1093 mk = new PdfDictionary(); 1094 item.getMerged(k).put(PdfName.MK, mk); 1095 item.getWidget(k).put(PdfName.MK, mk); 1096 markUsed(item.getWidget(k)); 1097 } else { 1098 markUsed( mk ); 1099 } 1100 if (value == null) 1101 mk.remove(dname); 1102 else 1103 mk.put(dname, PdfFormField.getMKColor((BaseColor)value)); 1104 } 1105 } 1106 } 1107 else 1108 return false; 1109 return true; 1110 } 1111 catch (Exception e) { 1112 throw new ExceptionConverter(e); 1113 } 1114 } 1115 1116 /** 1117 * Sets a field property. Valid property names are: 1118 * <p> 1119 * <ul> 1120 * <li>flags - a set of flags specifying various characteristics of the field's widget annotation. 1121 * The value of this entry replaces that of the F entry in the form's corresponding annotation dictionary.<br> 1122 * <li>setflags - a set of flags to be set (turned on) in the F entry of the form's corresponding 1123 * widget annotation dictionary. Bits equal to 1 cause the corresponding bits in F to be set to 1.<br> 1124 * <li>clrflags - a set of flags to be cleared (turned off) in the F entry of the form's corresponding 1125 * widget annotation dictionary. Bits equal to 1 cause the corresponding 1126 * bits in F to be set to 0.<br> 1127 * <li>fflags - a set of flags specifying various characteristics of the field. The value 1128 * of this entry replaces that of the Ff entry in the form's corresponding field dictionary.<br> 1129 * <li>setfflags - a set of flags to be set (turned on) in the Ff entry of the form's corresponding 1130 * field dictionary. Bits equal to 1 cause the corresponding bits in Ff to be set to 1.<br> 1131 * <li>clrfflags - a set of flags to be cleared (turned off) in the Ff entry of the form's corresponding 1132 * field dictionary. Bits equal to 1 cause the corresponding bits in Ff 1133 * to be set to 0.<br> 1134 * </ul> 1135 * 1136 * @param field the field name 1137 * @param name the property name 1138 * @param value the property value 1139 * @param inst an array of <CODE>int</CODE> indexing into <CODE>AcroField.Item.merged</CODE> elements to process. 1140 * Set to <CODE>null</CODE> to process all 1141 * @return <CODE>true</CODE> if the property exists, <CODE>false</CODE> otherwise 1142 */ 1143 public boolean setFieldProperty(String field, String name, int value, int inst[]) { 1144 if (writer == null) 1145 throw new RuntimeException(MessageLocalization.getComposedMessage("this.acrofields.instance.is.read.only")); 1146 Item item = fields.get(field); 1147 if (item == null) 1148 return false; 1149 InstHit hit = new InstHit(inst); 1150 if (name.equalsIgnoreCase("flags")) { 1151 PdfNumber num = new PdfNumber(value); 1152 for (int k = 0; k < item.size(); ++k) { 1153 if (hit.isHit(k)) { 1154 item.getMerged(k).put(PdfName.F, num); 1155 item.getWidget(k).put(PdfName.F, num); 1156 markUsed(item.getWidget(k)); 1157 } 1158 } 1159 } 1160 else if (name.equalsIgnoreCase("setflags")) { 1161 for (int k = 0; k < item.size(); ++k) { 1162 if (hit.isHit(k)) { 1163 PdfNumber num = item.getWidget(k).getAsNumber(PdfName.F); 1164 int val = 0; 1165 if (num != null) 1166 val = num.intValue(); 1167 num = new PdfNumber(val | value); 1168 item.getMerged(k).put(PdfName.F, num); 1169 item.getWidget(k).put(PdfName.F, num); 1170 markUsed(item.getWidget(k)); 1171 } 1172 } 1173 } 1174 else if (name.equalsIgnoreCase("clrflags")) { 1175 for (int k = 0; k < item.size(); ++k) { 1176 if (hit.isHit(k)) { 1177 PdfDictionary widget = item.getWidget( k ); 1178 PdfNumber num = widget.getAsNumber(PdfName.F); 1179 int val = 0; 1180 if (num != null) 1181 val = num.intValue(); 1182 num = new PdfNumber(val & ~value); 1183 item.getMerged(k).put(PdfName.F, num); 1184 widget.put(PdfName.F, num); 1185 markUsed(widget); 1186 } 1187 } 1188 } 1189 else if (name.equalsIgnoreCase("fflags")) { 1190 PdfNumber num = new PdfNumber(value); 1191 for (int k = 0; k < item.size(); ++k) { 1192 if (hit.isHit(k)) { 1193 item.getMerged(k).put(PdfName.FF, num); 1194 item.getValue(k).put(PdfName.FF, num); 1195 markUsed(item.getValue(k)); 1196 } 1197 } 1198 } 1199 else if (name.equalsIgnoreCase("setfflags")) { 1200 for (int k = 0; k < item.size(); ++k) { 1201 if (hit.isHit(k)) { 1202 PdfDictionary valDict = item.getValue( k ); 1203 PdfNumber num = valDict.getAsNumber( PdfName.FF ); 1204 int val = 0; 1205 if (num != null) 1206 val = num.intValue(); 1207 num = new PdfNumber(val | value); 1208 item.getMerged(k).put(PdfName.FF, num); 1209 valDict.put(PdfName.FF, num); 1210 markUsed(valDict); 1211 } 1212 } 1213 } 1214 else if (name.equalsIgnoreCase("clrfflags")) { 1215 for (int k = 0; k < item.size(); ++k) { 1216 if (hit.isHit(k)) { 1217 PdfDictionary valDict = item.getValue( k ); 1218 PdfNumber num = valDict.getAsNumber(PdfName.FF); 1219 int val = 0; 1220 if (num != null) 1221 val = num.intValue(); 1222 num = new PdfNumber(val & ~value); 1223 item.getMerged(k).put(PdfName.FF, num); 1224 valDict.put(PdfName.FF, num); 1225 markUsed(valDict); 1226 } 1227 } 1228 } 1229 else 1230 return false; 1231 return true; 1232 } 1233 1234 /** 1235 * Merges an XML data structure into this form. 1236 * 1237 * @param n the top node of the data structure 1238 * @throws java.io.IOException on error 1239 * @throws com.itextpdf.text.DocumentException o error 1240 */ 1241 public void mergeXfaData(Node n) throws IOException, DocumentException { 1242 XfaForm.Xml2SomDatasets data = new XfaForm.Xml2SomDatasets(n); 1243 for (String string : data.getOrder()) { 1244 String name = string; 1245 String text = XfaForm.getNodeText(data.getName2Node().get(name)); 1246 setField(name, text); 1247 } 1248 } 1249 1250 /** 1251 * Sets the fields by FDF merging. 1252 * 1253 * @param fdf the FDF form 1254 * @throws IOException on error 1255 * @throws DocumentException on error 1256 */ 1257 public void setFields(FdfReader fdf) throws IOException, DocumentException { 1258 HashMap<String, PdfDictionary> fd = fdf.getFields(); 1259 for (String f: fd.keySet()) { 1260 String v = fdf.getFieldValue(f); 1261 if (v != null) 1262 setField(f, v); 1263 } 1264 } 1265 1266 /** 1267 * Sets the fields by XFDF merging. 1268 * 1269 * @param xfdf the XFDF form 1270 * @throws IOException on error 1271 * @throws DocumentException on error 1272 */ 1273 public void setFields(XfdfReader xfdf) throws IOException, DocumentException { 1274 HashMap<String, String> fd = xfdf.getFields(); 1275 for (String f: fd.keySet()) { 1276 String v = xfdf.getFieldValue(f); 1277 if (v != null) 1278 setField(f, v); 1279 List<String> l = xfdf.getListValues(f); 1280 if (l != null) 1281 setListSelection(v, l.toArray(new String[l.size()])); 1282 } 1283 } 1284 1285 /** 1286 * Regenerates the field appearance. 1287 * This is useful when you change a field property, but not its value, 1288 * for instance form.setFieldProperty("f", "bgcolor", BaseColor.BLUE, null); 1289 * This won't have any effect, unless you use regenerateField("f") after changing 1290 * the property. 1291 * 1292 * @param name the fully qualified field name or the partial name in the case of XFA forms 1293 * @throws IOException on error 1294 * @throws DocumentException on error 1295 * @return <CODE>true</CODE> if the field was found and changed, 1296 * <CODE>false</CODE> otherwise 1297 */ 1298 public boolean regenerateField(String name) throws IOException, DocumentException { 1299 String value = getField(name); 1300 return setField(name, value, value); 1301 } 1302 1303 /** 1304 * Sets the field value. 1305 * 1306 * @param name the fully qualified field name or the partial name in the case of XFA forms 1307 * @param value the field value 1308 * @throws IOException on error 1309 * @throws DocumentException on error 1310 * @return <CODE>true</CODE> if the field was found and changed, 1311 * <CODE>false</CODE> otherwise 1312 */ 1313 public boolean setField(String name, String value) throws IOException, DocumentException { 1314 return setField(name, value, null); 1315 } 1316 1317 /** 1318 * Sets the rich value for the given field. See <a href="http://www.adobe.com/content/dam/Adobe/en/devnet/pdf/pdfs/PDF32000_2008.pdf">PDF Reference</a> chapter 1319 * 12.7.3.4 (Rich Text) and 12.7.4.3 (Text Fields) for further details. 1320 * @param name Field name 1321 * @param richValue html markup 1322 * @return success/failure (will fail if the field isn't found, isn't a text field, or doesn't support rich text) 1323 * @throws DocumentException 1324 * @since 5.0.6 1325 */ 1326 public boolean setFieldRichValue(String name, String richValue) throws DocumentException { 1327 if (writer == null) { 1328 // can't set field values: fail 1329 throw new DocumentException(MessageLocalization.getComposedMessage("this.acrofields.instance.is.read.only")); 1330 } 1331 1332 AcroFields.Item item = getFieldItem(name); 1333 if (item == null) { 1334 // can't find the field: fail. 1335 return false; 1336 } 1337 1338 if (getFieldType(name) != FIELD_TYPE_TEXT) { 1339 // field isn't a text field: fail 1340 return false; 1341 } 1342 1343 PdfDictionary merged = item.getMerged(0); 1344 PdfNumber ffNum = merged.getAsNumber(PdfName.FF); 1345 int flagVal = 0; 1346 if (ffNum != null) { 1347 flagVal = ffNum.intValue(); 1348 } 1349 if ((flagVal | PdfFormField.FF_RICHTEXT) == 0) { 1350 // text field doesn't support rich text: fail 1351 return false; 1352 } 1353 1354 PdfString richString = new PdfString(richValue); 1355 item.writeToAll(PdfName.RV, richString, Item.WRITE_MERGED | Item.WRITE_VALUE); 1356 1357 return true; 1358 } 1359 1360 /** 1361 * Sets the field value and the display string. The display string 1362 * is used to build the appearance in the cases where the value 1363 * is modified by Acrobat with JavaScript and the algorithm is 1364 * known. 1365 * 1366 * @param name the fully qualified field name or the partial name in the case of XFA forms 1367 * @param value the field value 1368 * @param display the string that is used for the appearance. If <CODE>null</CODE> 1369 * the <CODE>value</CODE> parameter will be used 1370 * @return <CODE>true</CODE> if the field was found and changed, 1371 * <CODE>false</CODE> otherwise 1372 * @throws IOException on error 1373 * @throws DocumentException on error 1374 */ 1375 public boolean setField(String name, String value, String display) throws IOException, DocumentException { 1376 if (writer == null) 1377 throw new DocumentException(MessageLocalization.getComposedMessage("this.acrofields.instance.is.read.only")); 1378 if (xfa.isXfaPresent()) { 1379 name = xfa.findFieldName(name, this); 1380 if (name == null) 1381 return false; 1382 String shortName = XfaForm.Xml2Som.getShortName(name); 1383 Node xn = xfa.findDatasetsNode(shortName); 1384 if (xn == null) { 1385 xn = xfa.getDatasetsSom().insertNode(xfa.getDatasetsNode(), shortName); 1386 } 1387 xfa.setNodeText(xn, value); 1388 } 1389 Item item = fields.get(name); 1390 if (item == null) 1391 return false; 1392 PdfDictionary merged = item.getMerged( 0 ); 1393 PdfName type = merged.getAsName(PdfName.FT); 1394 if (PdfName.TX.equals(type)) { 1395 PdfNumber maxLen = merged.getAsNumber(PdfName.MAXLEN); 1396 int len = 0; 1397 if (maxLen != null) 1398 len = maxLen.intValue(); 1399 if (len > 0) 1400 value = value.substring(0, Math.min(len, value.length())); 1401 } 1402 if (display == null) 1403 display = value; 1404 if (PdfName.TX.equals(type) || PdfName.CH.equals(type)) { 1405 PdfString v = new PdfString(value, PdfObject.TEXT_UNICODE); 1406 for (int idx = 0; idx < item.size(); ++idx) { 1407 PdfDictionary valueDic = item.getValue(idx); 1408 valueDic.put(PdfName.V, v); 1409 valueDic.remove(PdfName.I); 1410 markUsed(valueDic); 1411 merged = item.getMerged(idx); 1412 merged.remove(PdfName.I); 1413 merged.put(PdfName.V, v); 1414 PdfDictionary widget = item.getWidget(idx); 1415 if (generateAppearances) { 1416 PdfAppearance app = getAppearance(merged, display, name); 1417 if (PdfName.CH.equals(type)) { 1418 PdfNumber n = new PdfNumber(topFirst); 1419 widget.put(PdfName.TI, n); 1420 merged.put(PdfName.TI, n); 1421 } 1422 PdfDictionary appDic = widget.getAsDict(PdfName.AP); 1423 if (appDic == null) { 1424 appDic = new PdfDictionary(); 1425 widget.put(PdfName.AP, appDic); 1426 merged.put(PdfName.AP, appDic); 1427 } 1428 appDic.put(PdfName.N, app.getIndirectReference()); 1429 writer.releaseTemplate(app); 1430 } 1431 else { 1432 widget.remove(PdfName.AP); 1433 merged.remove(PdfName.AP); 1434 } 1435 markUsed(widget); 1436 } 1437 return true; 1438 } 1439 else if (PdfName.BTN.equals(type)) { 1440 PdfNumber ff = item.getMerged(0).getAsNumber(PdfName.FF); 1441 int flags = 0; 1442 if (ff != null) 1443 flags = ff.intValue(); 1444 if ((flags & PdfFormField.FF_PUSHBUTTON) != 0) { 1445 //we'll assume that the value is an image in base64 1446 Image img; 1447 try { 1448 img = Image.getInstance(Base64.decode(value)); 1449 } 1450 catch (Exception e) { 1451 return false; 1452 } 1453 PushbuttonField pb = getNewPushbuttonFromField(name); 1454 pb.setImage(img); 1455 replacePushbuttonField(name, pb.getField()); 1456 return true; 1457 } 1458 PdfName v = new PdfName(value); 1459 ArrayList<String> lopt = new ArrayList<String>(); 1460 PdfArray opts = item.getValue(0).getAsArray(PdfName.OPT); 1461 if (opts != null) { 1462 for (int k = 0; k < opts.size(); ++k) { 1463 PdfString valStr = opts.getAsString(k); 1464 if (valStr != null) 1465 lopt.add(valStr.toUnicodeString()); 1466 else 1467 lopt.add(null); 1468 } 1469 } 1470 int vidx = lopt.indexOf(value); 1471 PdfName vt; 1472 if (vidx >= 0) 1473 vt = new PdfName(String.valueOf(vidx)); 1474 else 1475 vt = v; 1476 for (int idx = 0; idx < item.size(); ++idx) { 1477 merged = item.getMerged(idx); 1478 PdfDictionary widget = item.getWidget(idx); 1479 PdfDictionary valDict = item.getValue(idx); 1480 markUsed(item.getValue(idx)); 1481 valDict.put(PdfName.V, vt); 1482 merged.put(PdfName.V, vt); 1483 markUsed(widget); 1484 if (isInAP(widget, vt)) { 1485 merged.put(PdfName.AS, vt); 1486 widget.put(PdfName.AS, vt); 1487 } 1488 else { 1489 merged.put(PdfName.AS, PdfName.Off); 1490 widget.put(PdfName.AS, PdfName.Off); 1491 } 1492 } 1493 return true; 1494 } 1495 return false; 1496 } 1497 1498 /** 1499 * Sets different values in a list selection. 1500 * No appearance is generated yet; nor does the code check if multiple select is allowed. 1501 * 1502 * @param name the name of the field 1503 * @param value an array with values that need to be selected 1504 * @return true only if the field value was changed 1505 * @since 2.1.4 1506 */ 1507 public boolean setListSelection(String name, String[] value) throws IOException, DocumentException { 1508 Item item = getFieldItem(name); 1509 if (item == null) 1510 return false; 1511 PdfDictionary merged = item.getMerged( 0 ); 1512 PdfName type = merged.getAsName(PdfName.FT); 1513 if (!PdfName.CH.equals(type)) { 1514 return false; 1515 } 1516 String[] options = getListOptionExport(name); 1517 PdfArray array = new PdfArray(); 1518 for (String element : value) { 1519 for (int j = 0; j < options.length; j++) { 1520 if (options[j].equals(element)) { 1521 array.add(new PdfNumber(j)); 1522 break; 1523 } 1524 } 1525 } 1526 item.writeToAll(PdfName.I, array, Item.WRITE_MERGED | Item.WRITE_VALUE); 1527 1528 PdfArray vals = new PdfArray(); 1529 for (int i = 0; i < value.length; ++i) { 1530 vals.add( new PdfString( value[i] ) ); 1531 } 1532 item.writeToAll(PdfName.V, vals, Item.WRITE_MERGED | Item.WRITE_VALUE); 1533 1534 PdfAppearance app = getAppearance( merged, value, name ); 1535 1536 PdfDictionary apDic = new PdfDictionary(); 1537 apDic.put( PdfName.N, app.getIndirectReference() ); 1538 item.writeToAll(PdfName.AP, apDic, Item.WRITE_MERGED | Item.WRITE_WIDGET); 1539 1540 writer.releaseTemplate( app ); 1541 1542 item.markUsed( this, Item.WRITE_VALUE | Item.WRITE_WIDGET ); 1543 return true; 1544 } 1545 1546 boolean isInAP(PdfDictionary dic, PdfName check) { 1547 PdfDictionary appDic = dic.getAsDict(PdfName.AP); 1548 if (appDic == null) 1549 return false; 1550 PdfDictionary NDic = appDic.getAsDict(PdfName.N); 1551 return NDic != null && NDic.get(check) != null; 1552 } 1553 1554 /** 1555 * Gets all the fields. The fields are keyed by the fully qualified field name and 1556 * the value is an instance of <CODE>AcroFields.Item</CODE>. 1557 * 1558 * @return all the fields 1559 */ 1560 public Map<String, Item> getFields() { 1561 return fields; 1562 } 1563 1564 /** 1565 * Gets the field structure. 1566 * 1567 * @param name the name of the field 1568 * @return the field structure or <CODE>null</CODE> if the field 1569 * does not exist 1570 */ 1571 public Item getFieldItem(String name) { 1572 if (xfa.isXfaPresent()) { 1573 name = xfa.findFieldName(name, this); 1574 if (name == null) 1575 return null; 1576 } 1577 return fields.get(name); 1578 } 1579 1580 /** 1581 * Gets the long XFA translated name. 1582 * 1583 * @param name the name of the field 1584 * @return the long field name 1585 */ 1586 public String getTranslatedFieldName(String name) { 1587 if (xfa.isXfaPresent()) { 1588 String namex = xfa.findFieldName(name, this); 1589 if (namex != null) 1590 name = namex; 1591 } 1592 return name; 1593 } 1594 1595 /** 1596 * Gets the field box positions in the document. The return is an array of <CODE>float</CODE> 1597 * multiple of 5. For each of this groups the values are: [page, llx, lly, urx, 1598 * ury]. The coordinates have the page rotation in consideration. 1599 * 1600 * @param name the field name 1601 * @return the positions or <CODE>null</CODE> if field does not exist 1602 */ 1603 public List<FieldPosition> getFieldPositions(String name) { 1604 Item item = getFieldItem(name); 1605 if (item == null) 1606 return null; 1607 ArrayList<FieldPosition> ret = new ArrayList<FieldPosition>(); 1608 for (int k = 0; k < item.size(); ++k) { 1609 try { 1610 PdfDictionary wd = item.getWidget(k); 1611 PdfArray rect = wd.getAsArray(PdfName.RECT); 1612 if (rect == null) 1613 continue; 1614 Rectangle r = PdfReader.getNormalizedRectangle(rect); 1615 int page = item.getPage(k).intValue(); 1616 int rotation = reader.getPageRotation(page); 1617 FieldPosition fp = new FieldPosition(); 1618 fp.page = page; 1619 if (rotation != 0) { 1620 Rectangle pageSize = reader.getPageSize(page); 1621 switch (rotation) { 1622 case 270: 1623 r = new Rectangle( 1624 pageSize.getTop() - r.getBottom(), 1625 r.getLeft(), 1626 pageSize.getTop() - r.getTop(), 1627 r.getRight()); 1628 break; 1629 case 180: 1630 r = new Rectangle( 1631 pageSize.getRight() - r.getLeft(), 1632 pageSize.getTop() - r.getBottom(), 1633 pageSize.getRight() - r.getRight(), 1634 pageSize.getTop() - r.getTop()); 1635 break; 1636 case 90: 1637 r = new Rectangle( 1638 r.getBottom(), 1639 pageSize.getRight() - r.getLeft(), 1640 r.getTop(), 1641 pageSize.getRight() - r.getRight()); 1642 break; 1643 } 1644 r.normalize(); 1645 } 1646 fp.position = r; 1647 ret.add(fp); 1648 } 1649 catch (Exception e) { 1650 // empty on purpose 1651 } 1652 } 1653 return ret; 1654 } 1655 1656 private int removeRefFromArray(PdfArray array, PdfObject refo) { 1657 if (refo == null || !refo.isIndirect()) 1658 return array.size(); 1659 PdfIndirectReference ref = (PdfIndirectReference)refo; 1660 for (int j = 0; j < array.size(); ++j) { 1661 PdfObject obj = array.getPdfObject(j); 1662 if (!obj.isIndirect()) 1663 continue; 1664 if (((PdfIndirectReference)obj).getNumber() == ref.getNumber()) 1665 array.remove(j--); 1666 } 1667 return array.size(); 1668 } 1669 1670 /** 1671 * Removes all the fields from <CODE>page</CODE>. 1672 * 1673 * @param page the page to remove the fields from 1674 * @return <CODE>true</CODE> if any field was removed, <CODE>false otherwise</CODE> 1675 */ 1676 public boolean removeFieldsFromPage(int page) { 1677 if (page < 1) 1678 return false; 1679 String names[] = new String[fields.size()]; 1680 fields.keySet().toArray(names); 1681 boolean found = false; 1682 for (int k = 0; k < names.length; ++k) { 1683 boolean fr = removeField(names[k], page); 1684 found = found || fr; 1685 } 1686 return found; 1687 } 1688 1689 /** 1690 * Removes a field from the document. If page equals -1 all the fields with this 1691 * <CODE>name</CODE> are removed from the document otherwise only the fields in 1692 * that particular page are removed. 1693 * 1694 * @param name the field name 1695 * @param page the page to remove the field from or -1 to remove it from all the pages 1696 * @return <CODE>true</CODE> if the field exists, <CODE>false otherwise</CODE> 1697 */ 1698 public boolean removeField(String name, int page) { 1699 Item item = getFieldItem(name); 1700 if (item == null) 1701 return false; 1702 PdfDictionary acroForm = (PdfDictionary)PdfReader.getPdfObject(reader.getCatalog().get(PdfName.ACROFORM), reader.getCatalog()); 1703 1704 if (acroForm == null) 1705 return false; 1706 PdfArray arrayf = acroForm.getAsArray(PdfName.FIELDS); 1707 if (arrayf == null) 1708 return false; 1709 for (int k = 0; k < item.size(); ++k) { 1710 int pageV = item.getPage(k).intValue(); 1711 if (page != -1 && page != pageV) 1712 continue; 1713 PdfIndirectReference ref = item.getWidgetRef(k); 1714 PdfDictionary wd = item.getWidget( k ); 1715 PdfDictionary pageDic = reader.getPageN(pageV); 1716 PdfArray annots = pageDic.getAsArray(PdfName.ANNOTS); 1717 if (annots != null) { 1718 if (removeRefFromArray(annots, ref) == 0) { 1719 pageDic.remove(PdfName.ANNOTS); 1720 markUsed(pageDic); 1721 } 1722 else 1723 markUsed(annots); 1724 } 1725 PdfReader.killIndirect(ref); 1726 PdfIndirectReference kid = ref; 1727 while ((ref = wd.getAsIndirectObject(PdfName.PARENT)) != null) { 1728 wd = wd.getAsDict( PdfName.PARENT ); 1729 PdfArray kids = wd.getAsArray(PdfName.KIDS); 1730 if (removeRefFromArray(kids, kid) != 0) 1731 break; 1732 kid = ref; 1733 PdfReader.killIndirect(ref); 1734 } 1735 if (ref == null) { 1736 removeRefFromArray(arrayf, kid); 1737 markUsed(arrayf); 1738 } 1739 if (page != -1) { 1740 item.remove( k ); 1741 --k; 1742 } 1743 } 1744 if (page == -1 || item.size() == 0) 1745 fields.remove(name); 1746 return true; 1747 } 1748 1749 /** 1750 * Removes a field from the document. 1751 * 1752 * @param name the field name 1753 * @return <CODE>true</CODE> if the field exists, <CODE>false otherwise</CODE> 1754 */ 1755 public boolean removeField(String name) { 1756 return removeField(name, -1); 1757 } 1758 1759 /** 1760 * Gets the property generateAppearances. 1761 * 1762 * @return the property generateAppearances 1763 */ 1764 public boolean isGenerateAppearances() { 1765 return generateAppearances; 1766 } 1767 1768 /** 1769 * Sets the option to generate appearances. Not generating appearances 1770 * will speed-up form filling but the results can be 1771 * unexpected in Acrobat. Don't use it unless your environment is well 1772 * controlled. The default is <CODE>true</CODE>. 1773 * 1774 * @param generateAppearances the option to generate appearances 1775 */ 1776 public void setGenerateAppearances(boolean generateAppearances) { 1777 this.generateAppearances = generateAppearances; 1778 PdfDictionary top = reader.getCatalog().getAsDict(PdfName.ACROFORM); 1779 if (generateAppearances) 1780 top.remove(PdfName.NEEDAPPEARANCES); 1781 else 1782 top.put(PdfName.NEEDAPPEARANCES, PdfBoolean.PDFTRUE); 1783 } 1784 1785 /** The field representations for retrieval and modification. */ 1786 public static class Item { 1787 1788 /** 1789 * <CODE>writeToAll</CODE> constant. 1790 * 1791 * @since 2.1.5 1792 */ 1793 public static final int WRITE_MERGED = 1; 1794 1795 /** 1796 * <CODE>writeToAll</CODE> and <CODE>markUsed</CODE> constant. 1797 * 1798 * @since 2.1.5 1799 */ 1800 public static final int WRITE_WIDGET = 2; 1801 1802 /** 1803 * <CODE>writeToAll</CODE> and <CODE>markUsed</CODE> constant. 1804 * 1805 * @since 2.1.5 1806 */ 1807 public static final int WRITE_VALUE = 4; 1808 1809 /** 1810 * This function writes the given key/value pair to all the instances 1811 * of merged, widget, and/or value, depending on the <code>writeFlags</code> setting 1812 * 1813 * @since 2.1.5 1814 * 1815 * @param key you'll never guess what this is for. 1816 * @param value if value is null, the key will be removed 1817 * @param writeFlags ORed together WRITE_* flags 1818 */ 1819 public void writeToAll(PdfName key, PdfObject value, int writeFlags) { 1820 int i; 1821 PdfDictionary curDict = null; 1822 if ((writeFlags & WRITE_MERGED) != 0) { 1823 for (i = 0; i < merged.size(); ++i) { 1824 curDict = getMerged(i); 1825 curDict.put(key, value); 1826 } 1827 } 1828 if ((writeFlags & WRITE_WIDGET) != 0) { 1829 for (i = 0; i < widgets.size(); ++i) { 1830 curDict = getWidget(i); 1831 curDict.put(key, value); 1832 } 1833 } 1834 if ((writeFlags & WRITE_VALUE) != 0) { 1835 for (i = 0; i < values.size(); ++i) { 1836 curDict = getValue(i); 1837 curDict.put(key, value); 1838 } 1839 } 1840 } 1841 1842 /** 1843 * Mark all the item dictionaries used matching the given flags 1844 * 1845 * @since 2.1.5 1846 * @param writeFlags WRITE_MERGED is ignored 1847 */ 1848 public void markUsed( AcroFields parentFields, int writeFlags ) { 1849 if ((writeFlags & WRITE_VALUE) != 0) { 1850 for (int i = 0; i < size(); ++i) { 1851 parentFields.markUsed( getValue( i ) ); 1852 } 1853 } 1854 if ((writeFlags & WRITE_WIDGET) != 0) { 1855 for (int i = 0; i < size(); ++i) { 1856 parentFields.markUsed(getWidget(i)); 1857 } 1858 } 1859 } 1860 1861 /** 1862 * An array of <CODE>PdfDictionary</CODE> where the value tag /V 1863 * is present. 1864 * 1865 * @since 5.0.2 public is now protected 1866 */ 1867 protected ArrayList<PdfDictionary> values = new ArrayList<PdfDictionary>(); 1868 1869 /** 1870 * An array of <CODE>PdfDictionary</CODE> with the widgets. 1871 * 1872 * @since 5.0.2 public is now protected 1873 */ 1874 protected ArrayList<PdfDictionary> widgets = new ArrayList<PdfDictionary>(); 1875 1876 /** 1877 * An array of <CODE>PdfDictionary</CODE> with the widget references. 1878 * 1879 * @since 5.0.2 public is now protected 1880 */ 1881 protected ArrayList<PdfIndirectReference> widget_refs = new ArrayList<PdfIndirectReference>(); 1882 1883 /** 1884 * An array of <CODE>PdfDictionary</CODE> with all the field 1885 * and widget tags merged. 1886 * 1887 * @since 5.0.2 public is now protected 1888 */ 1889 protected ArrayList<PdfDictionary> merged = new ArrayList<PdfDictionary>(); 1890 1891 /** 1892 * An array of <CODE>Integer</CODE> with the page numbers where 1893 * the widgets are displayed. 1894 * 1895 * @since 5.0.2 public is now protected 1896 */ 1897 protected ArrayList<Integer> page = new ArrayList<Integer>(); 1898 /** 1899 * An array of <CODE>Integer</CODE> with the tab order of the field in the page. 1900 * 1901 * @since 5.0.2 public is now protected 1902 */ 1903 protected ArrayList<Integer> tabOrder = new ArrayList<Integer>(); 1904 1905 /** 1906 * Preferred method of determining the number of instances 1907 * of a given field. 1908 * 1909 * @since 2.1.5 1910 * @return number of instances 1911 */ 1912 public int size() { 1913 return values.size(); 1914 } 1915 1916 /** 1917 * Remove the given instance from this item. It is possible to 1918 * remove all instances using this function. 1919 * 1920 * @since 2.1.5 1921 * @param killIdx 1922 */ 1923 void remove(int killIdx) { 1924 values.remove(killIdx); 1925 widgets.remove(killIdx); 1926 widget_refs.remove(killIdx); 1927 merged.remove(killIdx); 1928 page.remove(killIdx); 1929 tabOrder.remove(killIdx); 1930 } 1931 1932 /** 1933 * Retrieve the value dictionary of the given instance 1934 * 1935 * @since 2.1.5 1936 * @param idx instance index 1937 * @return dictionary storing this instance's value. It may be shared across instances. 1938 */ 1939 public PdfDictionary getValue(int idx) { 1940 return values.get(idx); 1941 } 1942 1943 /** 1944 * Add a value dict to this Item 1945 * 1946 * @since 2.1.5 1947 * @param value new value dictionary 1948 */ 1949 void addValue(PdfDictionary value) { 1950 values.add(value); 1951 } 1952 1953 /** 1954 * Retrieve the widget dictionary of the given instance 1955 * 1956 * @since 2.1.5 1957 * @param idx instance index 1958 * @return The dictionary found in the appropriate page's Annot array. 1959 */ 1960 public PdfDictionary getWidget(int idx) { 1961 return widgets.get(idx); 1962 } 1963 1964 /** 1965 * Add a widget dict to this Item 1966 * 1967 * @since 2.1.5 1968 * @param widget 1969 */ 1970 void addWidget(PdfDictionary widget) { 1971 widgets.add(widget); 1972 } 1973 1974 /** 1975 * Retrieve the reference to the given instance 1976 * 1977 * @since 2.1.5 1978 * @param idx instance index 1979 * @return reference to the given field instance 1980 */ 1981 public PdfIndirectReference getWidgetRef(int idx) { 1982 return widget_refs.get(idx); 1983 } 1984 1985 /** 1986 * Add a widget ref to this Item 1987 * 1988 * @since 2.1.5 1989 * @param widgRef 1990 */ 1991 void addWidgetRef(PdfIndirectReference widgRef) { 1992 widget_refs.add(widgRef); 1993 } 1994 1995 /** 1996 * Retrieve the merged dictionary for the given instance. The merged 1997 * dictionary contains all the keys present in parent fields, though they 1998 * may have been overwritten (or modified?) by children. 1999 * Example: a merged radio field dict will contain /V 2000 * 2001 * @since 2.1.5 2002 * @param idx instance index 2003 * @return the merged dictionary for the given instance 2004 */ 2005 public PdfDictionary getMerged(int idx) { 2006 return merged.get(idx); 2007 } 2008 2009 /** 2010 * Adds a merged dictionary to this Item. 2011 * 2012 * @since 2.1.5 2013 * @param mergeDict 2014 */ 2015 void addMerged(PdfDictionary mergeDict) { 2016 merged.add(mergeDict); 2017 } 2018 2019 /** 2020 * Retrieve the page number of the given instance 2021 * 2022 * @since 2.1.5 2023 * @param idx 2024 * @return remember, pages are "1-indexed", not "0-indexed" like field instances. 2025 */ 2026 public Integer getPage(int idx) { 2027 return page.get(idx); 2028 } 2029 2030 /** 2031 * Adds a page to the current Item. 2032 * 2033 * @since 2.1.5 2034 * @param pg 2035 */ 2036 void addPage(int pg) { 2037 page.add(Integer.valueOf(pg)); 2038 } 2039 2040 /** 2041 * forces a page value into the Item. 2042 * 2043 * @since 2.1.5 2044 * @param idx 2045 */ 2046 void forcePage(int idx, int pg) { 2047 page.set(idx, Integer.valueOf( pg )); 2048 } 2049 2050 /** 2051 * Gets the tabOrder. 2052 * 2053 * @since 2.1.5 2054 * @param idx 2055 * @return tab index of the given field instance 2056 */ 2057 public Integer getTabOrder(int idx) { 2058 return tabOrder.get(idx); 2059 } 2060 2061 /** 2062 * Adds a tab order value to this Item. 2063 * 2064 * @since 2.1.5 2065 * @param order 2066 */ 2067 void addTabOrder(int order) { 2068 tabOrder.add(Integer.valueOf(order)); 2069 } 2070 } 2071 2072 private static class InstHit { 2073 IntHashtable hits; 2074 public InstHit(int inst[]) { 2075 if (inst == null) 2076 return; 2077 hits = new IntHashtable(); 2078 for (int k = 0; k < inst.length; ++k) 2079 hits.put(inst[k], 1); 2080 } 2081 2082 public boolean isHit(int n) { 2083 if (hits == null) 2084 return true; 2085 return hits.containsKey(n); 2086 } 2087 } 2088 2089 /** 2090 * Clears a signed field. 2091 * @param name the field name 2092 * @return true if the field was signed, false if the field was not signed or not found 2093 * @since 5.0.5 2094 */ 2095 public boolean clearSignatureField(String name) { 2096 sigNames = null; 2097 getSignatureNames(); 2098 if (!sigNames.containsKey(name)) 2099 return false; 2100 Item sig = fields.get(name); 2101 sig.markUsed(this, Item.WRITE_VALUE | Item.WRITE_WIDGET); 2102 int n = sig.size(); 2103 for (int k = 0; k < n; ++k) { 2104 clearSigDic(sig.getMerged(k)); 2105 clearSigDic(sig.getWidget(k)); 2106 clearSigDic(sig.getValue(k)); 2107 } 2108 return true; 2109 } 2110 2111 private static void clearSigDic(PdfDictionary dic) { 2112 dic.remove(PdfName.AP); 2113 dic.remove(PdfName.AS); 2114 dic.remove(PdfName.V); 2115 dic.remove(PdfName.DV); 2116 dic.remove(PdfName.SV); 2117 dic.remove(PdfName.FF); 2118 dic.put(PdfName.F, new PdfNumber(PdfAnnotation.FLAGS_PRINT)); 2119 } 2120 2121 /** 2122 * Gets the field names that have signatures and are signed. 2123 * 2124 * @return the field names that have signatures and are signed 2125 */ 2126 public ArrayList<String> getSignatureNames() { 2127 if (sigNames != null) 2128 return new ArrayList<String>(sigNames.keySet()); 2129 sigNames = new HashMap<String, int[]>(); 2130 ArrayList<Object[]> sorter = new ArrayList<Object[]>(); 2131 for (Map.Entry<String, Item> entry: fields.entrySet()) { 2132 Item item = entry.getValue(); 2133 PdfDictionary merged = item.getMerged(0); 2134 if (!PdfName.SIG.equals(merged.get(PdfName.FT))) 2135 continue; 2136 PdfDictionary v = merged.getAsDict(PdfName.V); 2137 if (v == null) 2138 continue; 2139 PdfString contents = v.getAsString(PdfName.CONTENTS); 2140 if (contents == null) 2141 continue; 2142 PdfArray ro = v.getAsArray(PdfName.BYTERANGE); 2143 if (ro == null) 2144 continue; 2145 int rangeSize = ro.size(); 2146 if (rangeSize < 2) 2147 continue; 2148 int length = ro.getAsNumber(rangeSize - 1).intValue() + ro.getAsNumber(rangeSize - 2).intValue(); 2149 sorter.add(new Object[]{entry.getKey(), new int[]{length, 0}}); 2150 } 2151 Collections.sort(sorter, new AcroFields.SorterComparator()); 2152 if (!sorter.isEmpty()) { 2153 if (((int[])sorter.get(sorter.size() - 1)[1])[0] == reader.getFileLength()) 2154 totalRevisions = sorter.size(); 2155 else 2156 totalRevisions = sorter.size() + 1; 2157 for (int k = 0; k < sorter.size(); ++k) { 2158 Object objs[] = sorter.get(k); 2159 String name = (String)objs[0]; 2160 int p[] = (int[])objs[1]; 2161 p[1] = k + 1; 2162 sigNames.put(name, p); 2163 } 2164 } 2165 return new ArrayList<String>(sigNames.keySet()); 2166 } 2167 2168 /** 2169 * Gets the field names that have blank signatures. 2170 * 2171 * @return the field names that have blank signatures 2172 */ 2173 public ArrayList<String> getBlankSignatureNames() { 2174 getSignatureNames(); 2175 ArrayList<String> sigs = new ArrayList<String>(); 2176 for (Map.Entry<String, Item> entry: fields.entrySet()) { 2177 Item item = entry.getValue(); 2178 PdfDictionary merged = item.getMerged(0); 2179 if (!PdfName.SIG.equals(merged.getAsName(PdfName.FT))) 2180 continue; 2181 if (sigNames.containsKey(entry.getKey())) 2182 continue; 2183 sigs.add(entry.getKey()); 2184 } 2185 return sigs; 2186 } 2187 2188 /** 2189 * Gets the signature dictionary, the one keyed by /V. 2190 * 2191 * @param name the field name 2192 * @return the signature dictionary keyed by /V or <CODE>null</CODE> if the field is not 2193 * a signature 2194 */ 2195 public PdfDictionary getSignatureDictionary(String name) { 2196 getSignatureNames(); 2197 name = getTranslatedFieldName(name); 2198 if (!sigNames.containsKey(name)) 2199 return null; 2200 Item item = fields.get(name); 2201 PdfDictionary merged = item.getMerged(0); 2202 return merged.getAsDict(PdfName.V); 2203 } 2204 2205 /** 2206 * Checks is the signature covers the entire document or just part of it. 2207 * 2208 * @param name the signature field name 2209 * @return <CODE>true</CODE> if the signature covers the entire document, 2210 * <CODE>false</CODE> otherwise 2211 */ 2212 public boolean signatureCoversWholeDocument(String name) { 2213 getSignatureNames(); 2214 name = getTranslatedFieldName(name); 2215 if (!sigNames.containsKey(name)) 2216 return false; 2217 return sigNames.get(name)[0] == reader.getFileLength(); 2218 } 2219 2220 /** 2221 * Verifies a signature. An example usage is: 2222 * <p> 2223 * <pre> 2224 * KeyStore kall = PdfPKCS7.loadCacertsKeyStore(); 2225 * PdfReader reader = new PdfReader("my_signed_doc.pdf"); 2226 * AcroFields af = reader.getAcroFields(); 2227 * ArrayList names = af.getSignatureNames(); 2228 * for (int k = 0; k < names.size(); ++k) { 2229 * String name = (String)names.get(k); 2230 * System.out.println("Signature name: " + name); 2231 * System.out.println("Signature covers whole document: " + af.signatureCoversWholeDocument(name)); 2232 * PdfPKCS7 pk = af.verifySignature(name); 2233 * Calendar cal = pk.getSignDate(); 2234 * Certificate pkc[] = pk.getCertificates(); 2235 * System.out.println("Subject: " + PdfPKCS7.getSubjectFields(pk.getSigningCertificate())); 2236 * System.out.println("Document modified: " + !pk.verify()); 2237 * Object fails[] = PdfPKCS7.verifyCertificates(pkc, kall, null, cal); 2238 * if (fails == null) 2239 * System.out.println("Certificates verified against the KeyStore"); 2240 * else 2241 * System.out.println("Certificate failed: " + fails[1]); 2242 * } 2243 * </pre> 2244 * 2245 * @param name the signature field name 2246 * @return a <CODE>PdfPKCS7</CODE> class to continue the verification 2247 */ 2248 public PdfPKCS7 verifySignature(String name) { 2249 return verifySignature(name, null); 2250 } 2251 2252 /** 2253 * Verifies a signature. An example usage is: 2254 * <p> 2255 * <pre> 2256 * KeyStore kall = PdfPKCS7.loadCacertsKeyStore(); 2257 * PdfReader reader = new PdfReader("my_signed_doc.pdf"); 2258 * AcroFields af = reader.getAcroFields(); 2259 * ArrayList names = af.getSignatureNames(); 2260 * for (int k = 0; k < names.size(); ++k) { 2261 * String name = (String)names.get(k); 2262 * System.out.println("Signature name: " + name); 2263 * System.out.println("Signature covers whole document: " + af.signatureCoversWholeDocument(name)); 2264 * PdfPKCS7 pk = af.verifySignature(name); 2265 * Calendar cal = pk.getSignDate(); 2266 * Certificate pkc[] = pk.getCertificates(); 2267 * System.out.println("Subject: " + PdfPKCS7.getSubjectFields(pk.getSigningCertificate())); 2268 * System.out.println("Document modified: " + !pk.verify()); 2269 * Object fails[] = PdfPKCS7.verifyCertificates(pkc, kall, null, cal); 2270 * if (fails == null) 2271 * System.out.println("Certificates verified against the KeyStore"); 2272 * else 2273 * System.out.println("Certificate failed: " + fails[1]); 2274 * } 2275 * </pre> 2276 * 2277 * @param name the signature field name 2278 * @param provider the provider or <code>null</code> for the default provider 2279 * @return a <CODE>PdfPKCS7</CODE> class to continue the verification 2280 */ 2281 public PdfPKCS7 verifySignature(String name, String provider) { 2282 PdfDictionary v = getSignatureDictionary(name); 2283 if (v == null) 2284 return null; 2285 try { 2286 PdfName sub = v.getAsName(PdfName.SUBFILTER); 2287 PdfString contents = v.getAsString(PdfName.CONTENTS); 2288 PdfPKCS7 pk = null; 2289 if (sub.equals(PdfName.ADBE_X509_RSA_SHA1)) { 2290 PdfString cert = v.getAsString(PdfName.CERT); 2291 if (cert == null) 2292 cert = v.getAsArray(PdfName.CERT).getAsString(0); 2293 pk = new PdfPKCS7(contents.getOriginalBytes(), cert.getBytes(), provider); 2294 } 2295 else 2296 pk = new PdfPKCS7(contents.getOriginalBytes(), provider); 2297 updateByteRange(pk, v); 2298 PdfString str = v.getAsString(PdfName.M); 2299 if (str != null) 2300 pk.setSignDate(PdfDate.decode(str.toString())); 2301 PdfObject obj = PdfReader.getPdfObject(v.get(PdfName.NAME)); 2302 if (obj != null) { 2303 if (obj.isString()) 2304 pk.setSignName(((PdfString)obj).toUnicodeString()); 2305 else if(obj.isName()) 2306 pk.setSignName(PdfName.decodeName(obj.toString())); 2307 } 2308 str = v.getAsString(PdfName.REASON); 2309 if (str != null) 2310 pk.setReason(str.toUnicodeString()); 2311 str = v.getAsString(PdfName.LOCATION); 2312 if (str != null) 2313 pk.setLocation(str.toUnicodeString()); 2314 return pk; 2315 } 2316 catch (Exception e) { 2317 throw new ExceptionConverter(e); 2318 } 2319 } 2320 2321 private void updateByteRange(PdfPKCS7 pkcs7, PdfDictionary v) { 2322 PdfArray b = v.getAsArray(PdfName.BYTERANGE); 2323 RandomAccessFileOrArray rf = reader.getSafeFile(); 2324 try { 2325 rf.reOpen(); 2326 byte buf[] = new byte[8192]; 2327 for (int k = 0; k < b.size(); ++k) { 2328 int start = b.getAsNumber(k).intValue(); 2329 int length = b.getAsNumber(++k).intValue(); 2330 rf.seek(start); 2331 while (length > 0) { 2332 int rd = rf.read(buf, 0, Math.min(length, buf.length)); 2333 if (rd <= 0) 2334 break; 2335 length -= rd; 2336 pkcs7.update(buf, 0, rd); 2337 } 2338 } 2339 } 2340 catch (Exception e) { 2341 throw new ExceptionConverter(e); 2342 } 2343 finally { 2344 try{rf.close();}catch(Exception e){} 2345 } 2346 } 2347 2348 private void markUsed(PdfObject obj) { 2349 if (!append) 2350 return; 2351 ((PdfStamperImp)writer).markUsed(obj); 2352 } 2353 2354 /** 2355 * Gets the total number of revisions this document has. 2356 * 2357 * @return the total number of revisions 2358 */ 2359 public int getTotalRevisions() { 2360 getSignatureNames(); 2361 return this.totalRevisions; 2362 } 2363 2364 /** 2365 * Gets this <CODE>field</CODE> revision. 2366 * 2367 * @param field the signature field name 2368 * @return the revision or zero if it's not a signature field 2369 */ 2370 public int getRevision(String field) { 2371 getSignatureNames(); 2372 field = getTranslatedFieldName(field); 2373 if (!sigNames.containsKey(field)) 2374 return 0; 2375 return sigNames.get(field)[1]; 2376 } 2377 2378 /** 2379 * Extracts a revision from the document. 2380 * 2381 * @param field the signature field name 2382 * @return an <CODE>InputStream</CODE> covering the revision. Returns <CODE>null</CODE> if 2383 * it's not a signature field 2384 * @throws IOException on error 2385 */ 2386 public InputStream extractRevision(String field) throws IOException { 2387 getSignatureNames(); 2388 field = getTranslatedFieldName(field); 2389 if (!sigNames.containsKey(field)) 2390 return null; 2391 int length = sigNames.get(field)[0]; 2392 RandomAccessFileOrArray raf = reader.getSafeFile(); 2393 raf.reOpen(); 2394 raf.seek(0); 2395 return new RevisionStream(raf, length); 2396 } 2397 2398 /** 2399 * Gets the appearances cache. 2400 * 2401 * @return the appearances cache 2402 * @since 2.1.5 this method used to return a HashMap 2403 */ 2404 public Map<String, TextField> getFieldCache() { 2405 return this.fieldCache; 2406 } 2407 2408 /** 2409 * Sets a cache for field appearances. Parsing the existing PDF to 2410 * create a new TextField is time expensive. For those tasks that repeatedly 2411 * fill the same PDF with different field values the use of the cache has dramatic 2412 * speed advantages. An example usage: 2413 * <p> 2414 * <pre> 2415 * String pdfFile = ...;// the pdf file used as template 2416 * ArrayList xfdfFiles = ...;// the xfdf file names 2417 * ArrayList pdfOutFiles = ...;// the output file names, one for each element in xpdfFiles 2418 * HashMap cache = new HashMap();// the appearances cache 2419 * PdfReader originalReader = new PdfReader(pdfFile); 2420 * for (int k = 0; k < xfdfFiles.size(); ++k) { 2421 * PdfReader reader = new PdfReader(originalReader); 2422 * XfdfReader xfdf = new XfdfReader((String)xfdfFiles.get(k)); 2423 * PdfStamper stp = new PdfStamper(reader, new FileOutputStream((String)pdfOutFiles.get(k))); 2424 * AcroFields af = stp.getAcroFields(); 2425 * af.setFieldCache(cache); 2426 * af.setFields(xfdf); 2427 * stp.close(); 2428 * } 2429 * </pre> 2430 * 2431 * @param fieldCache a Map that will carry the cached appearances 2432 * @since 2.1.5 this method used to take a HashMap as parameter 2433 */ 2434 public void setFieldCache(Map<String, TextField> fieldCache) { 2435 this.fieldCache = fieldCache; 2436 } 2437 2438 /** 2439 * Sets extra margins in text fields to better mimic the Acrobat layout. 2440 * 2441 * @param extraMarginLeft the extra margin left 2442 * @param extraMarginTop the extra margin top 2443 */ 2444 public void setExtraMargin(float extraMarginLeft, float extraMarginTop) { 2445 this.extraMarginLeft = extraMarginLeft; 2446 this.extraMarginTop = extraMarginTop; 2447 } 2448 2449 /** 2450 * Adds a substitution font to the list. The fonts in this list will be used if the original 2451 * font doesn't contain the needed glyphs. 2452 * 2453 * @param font the font 2454 */ 2455 public void addSubstitutionFont(BaseFont font) { 2456 if (substitutionFonts == null) 2457 substitutionFonts = new ArrayList<BaseFont>(); 2458 substitutionFonts.add(font); 2459 } 2460 2461 private static final HashMap<String, String[]> stdFieldFontNames = new HashMap<String, String[]>(); 2462 2463 /** 2464 * Holds value of property totalRevisions. 2465 */ 2466 private int totalRevisions; 2467 2468 /** 2469 * Holds value of property fieldCache. 2470 * 2471 * @since 2.1.5 this used to be a HashMap 2472 */ 2473 private Map<String, TextField> fieldCache; 2474 2475 static { 2476 stdFieldFontNames.put("CoBO", new String[]{"Courier-BoldOblique"}); 2477 stdFieldFontNames.put("CoBo", new String[]{"Courier-Bold"}); 2478 stdFieldFontNames.put("CoOb", new String[]{"Courier-Oblique"}); 2479 stdFieldFontNames.put("Cour", new String[]{"Courier"}); 2480 stdFieldFontNames.put("HeBO", new String[]{"Helvetica-BoldOblique"}); 2481 stdFieldFontNames.put("HeBo", new String[]{"Helvetica-Bold"}); 2482 stdFieldFontNames.put("HeOb", new String[]{"Helvetica-Oblique"}); 2483 stdFieldFontNames.put("Helv", new String[]{"Helvetica"}); 2484 stdFieldFontNames.put("Symb", new String[]{"Symbol"}); 2485 stdFieldFontNames.put("TiBI", new String[]{"Times-BoldItalic"}); 2486 stdFieldFontNames.put("TiBo", new String[]{"Times-Bold"}); 2487 stdFieldFontNames.put("TiIt", new String[]{"Times-Italic"}); 2488 stdFieldFontNames.put("TiRo", new String[]{"Times-Roman"}); 2489 stdFieldFontNames.put("ZaDb", new String[]{"ZapfDingbats"}); 2490 stdFieldFontNames.put("HySm", new String[]{"HYSMyeongJo-Medium", "UniKS-UCS2-H"}); 2491 stdFieldFontNames.put("HyGo", new String[]{"HYGoThic-Medium", "UniKS-UCS2-H"}); 2492 stdFieldFontNames.put("KaGo", new String[]{"HeiseiKakuGo-W5", "UniKS-UCS2-H"}); 2493 stdFieldFontNames.put("KaMi", new String[]{"HeiseiMin-W3", "UniJIS-UCS2-H"}); 2494 stdFieldFontNames.put("MHei", new String[]{"MHei-Medium", "UniCNS-UCS2-H"}); 2495 stdFieldFontNames.put("MSun", new String[]{"MSung-Light", "UniCNS-UCS2-H"}); 2496 stdFieldFontNames.put("STSo", new String[]{"STSong-Light", "UniGB-UCS2-H"}); 2497 } 2498 2499 private static class RevisionStream extends InputStream { 2500 private byte b[] = new byte[1]; 2501 private RandomAccessFileOrArray raf; 2502 private int length; 2503 private int rangePosition = 0; 2504 private boolean closed; 2505 2506 private RevisionStream(RandomAccessFileOrArray raf, int length) { 2507 this.raf = raf; 2508 this.length = length; 2509 } 2510 2511 @Override 2512 public int read() throws IOException { 2513 int n = read(b); 2514 if (n != 1) 2515 return -1; 2516 return b[0] & 0xff; 2517 } 2518 2519 @Override 2520 public int read(byte[] b, int off, int len) throws IOException { 2521 if (b == null) { 2522 throw new NullPointerException(); 2523 } else if (off < 0 || off > b.length || len < 0 || 2524 off + len > b.length || off + len < 0) { 2525 throw new IndexOutOfBoundsException(); 2526 } else if (len == 0) { 2527 return 0; 2528 } 2529 if (rangePosition >= length) { 2530 close(); 2531 return -1; 2532 } 2533 int elen = Math.min(len, length - rangePosition); 2534 raf.readFully(b, off, elen); 2535 rangePosition += elen; 2536 return elen; 2537 } 2538 2539 @Override 2540 public void close() throws IOException { 2541 if (!closed) { 2542 raf.close(); 2543 closed = true; 2544 } 2545 } 2546 } 2547 2548 private static class SorterComparator implements Comparator<Object[]> { 2549 public int compare(Object[] o1, Object[] o2) { 2550 int n1 = ((int[])o1[1])[0]; 2551 int n2 = ((int[])o2[1])[0]; 2552 return n1 - n2; 2553 } 2554 } 2555 2556 /** 2557 * 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 2558 * font doesn't contain the needed glyphs. 2559 * 2560 * @return the list 2561 */ 2562 public ArrayList<BaseFont> getSubstitutionFonts() { 2563 return substitutionFonts; 2564 } 2565 2566 /** 2567 * 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 2568 * font doesn't contain the needed glyphs. 2569 * 2570 * @param substitutionFonts the list 2571 */ 2572 public void setSubstitutionFonts(ArrayList<BaseFont> substitutionFonts) { 2573 this.substitutionFonts = substitutionFonts; 2574 } 2575 2576 /** 2577 * Gets the XFA form processor. 2578 * 2579 * @return the XFA form processor 2580 */ 2581 public XfaForm getXfa() { 2582 return xfa; 2583 } 2584 2585 /** 2586 * Removes the XFA stream from the document. 2587 */ 2588 public void removeXfa() { 2589 PdfDictionary root = reader.getCatalog(); 2590 PdfDictionary acroform = root.getAsDict(PdfName.ACROFORM); 2591 acroform.remove(PdfName.XFA); 2592 try { 2593 xfa = new XfaForm(reader); 2594 } 2595 catch(Exception e) { 2596 throw new ExceptionConverter(e); 2597 } 2598 } 2599 2600 private static final PdfName[] buttonRemove = {PdfName.MK, PdfName.F , PdfName.FF , PdfName.Q , PdfName.BS , PdfName.BORDER}; 2601 2602 /** 2603 * Creates a new pushbutton from an existing field. If there are several pushbuttons with the same name 2604 * only the first one is used. This pushbutton can be changed and be used to replace 2605 * an existing one, with the same name or other name, as long is it is in the same document. To replace an existing pushbutton 2606 * call {@link #replacePushbuttonField(String,PdfFormField)}. 2607 * 2608 * @param field the field name that should be a pushbutton 2609 * @return a new pushbutton or <CODE>null</CODE> if the field is not a pushbutton 2610 */ 2611 public PushbuttonField getNewPushbuttonFromField(String field) { 2612 return getNewPushbuttonFromField(field, 0); 2613 } 2614 2615 /** 2616 * Creates a new pushbutton from an existing field. This pushbutton can be changed and be used to replace 2617 * an existing one, with the same name or other name, as long is it is in the same document. To replace an existing pushbutton 2618 * call {@link #replacePushbuttonField(String,PdfFormField,int)}. 2619 * 2620 * @param field the field name that should be a pushbutton 2621 * @param order the field order in fields with same name 2622 * @return a new pushbutton or <CODE>null</CODE> if the field is not a pushbutton 2623 * 2624 * @since 2.0.7 2625 */ 2626 public PushbuttonField getNewPushbuttonFromField(String field, int order) { 2627 try { 2628 if (getFieldType(field) != FIELD_TYPE_PUSHBUTTON) 2629 return null; 2630 Item item = getFieldItem(field); 2631 if (order >= item.size()) 2632 return null; 2633 List<FieldPosition> pos = getFieldPositions(field); 2634 Rectangle box = pos.get(order).position; 2635 PushbuttonField newButton = new PushbuttonField(writer, box, null); 2636 PdfDictionary dic = item.getMerged(order); 2637 decodeGenericDictionary(dic, newButton); 2638 PdfDictionary mk = dic.getAsDict(PdfName.MK); 2639 if (mk != null) { 2640 PdfString text = mk.getAsString(PdfName.CA); 2641 if (text != null) 2642 newButton.setText(text.toUnicodeString()); 2643 PdfNumber tp = mk.getAsNumber(PdfName.TP); 2644 if (tp != null) 2645 newButton.setLayout(tp.intValue() + 1); 2646 PdfDictionary ifit = mk.getAsDict(PdfName.IF); 2647 if (ifit != null) { 2648 PdfName sw = ifit.getAsName(PdfName.SW); 2649 if (sw != null) { 2650 int scale = PushbuttonField.SCALE_ICON_ALWAYS; 2651 if (sw.equals(PdfName.B)) 2652 scale = PushbuttonField.SCALE_ICON_IS_TOO_BIG; 2653 else if (sw.equals(PdfName.S)) 2654 scale = PushbuttonField.SCALE_ICON_IS_TOO_SMALL; 2655 else if (sw.equals(PdfName.N)) 2656 scale = PushbuttonField.SCALE_ICON_NEVER; 2657 newButton.setScaleIcon(scale); 2658 } 2659 sw = ifit.getAsName(PdfName.S); 2660 if (sw != null) { 2661 if (sw.equals(PdfName.A)) 2662 newButton.setProportionalIcon(false); 2663 } 2664 PdfArray aj = ifit.getAsArray(PdfName.A); 2665 if (aj != null && aj.size() == 2) { 2666 float left = aj.getAsNumber(0).floatValue(); 2667 float bottom = aj.getAsNumber(1).floatValue(); 2668 newButton.setIconHorizontalAdjustment(left); 2669 newButton.setIconVerticalAdjustment(bottom); 2670 } 2671 PdfBoolean fb = ifit.getAsBoolean(PdfName.FB); 2672 if (fb != null && fb.booleanValue()) 2673 newButton.setIconFitToBounds(true); 2674 } 2675 PdfObject i = mk.get(PdfName.I); 2676 if (i != null && i.isIndirect()) 2677 newButton.setIconReference((PRIndirectReference)i); 2678 } 2679 return newButton; 2680 } 2681 catch (Exception e) { 2682 throw new ExceptionConverter(e); 2683 } 2684 } 2685 2686 /** 2687 * Replaces the first field with a new pushbutton. The pushbutton can be created with 2688 * {@link #getNewPushbuttonFromField(String)} from the same document or it can be a 2689 * generic PdfFormField of the type pushbutton. 2690 * 2691 * @param field the field name 2692 * @param button the <CODE>PdfFormField</CODE> representing the pushbutton 2693 * @return <CODE>true</CODE> if the field was replaced, <CODE>false</CODE> if the field 2694 * was not a pushbutton 2695 */ 2696 public boolean replacePushbuttonField(String field, PdfFormField button) { 2697 return replacePushbuttonField(field, button, 0); 2698 } 2699 2700 /** 2701 * Replaces the designated field with a new pushbutton. The pushbutton can be created with 2702 * {@link #getNewPushbuttonFromField(String,int)} from the same document or it can be a 2703 * generic PdfFormField of the type pushbutton. 2704 * 2705 * @param field the field name 2706 * @param button the <CODE>PdfFormField</CODE> representing the pushbutton 2707 * @param order the field order in fields with same name 2708 * @return <CODE>true</CODE> if the field was replaced, <CODE>false</CODE> if the field 2709 * was not a pushbutton 2710 * 2711 * @since 2.0.7 2712 */ 2713 public boolean replacePushbuttonField(String field, PdfFormField button, int order) { 2714 if (getFieldType(field) != FIELD_TYPE_PUSHBUTTON) 2715 return false; 2716 Item item = getFieldItem(field); 2717 if (order >= item.size()) 2718 return false; 2719 PdfDictionary merged = item.getMerged(order); 2720 PdfDictionary values = item.getValue(order); 2721 PdfDictionary widgets = item.getWidget(order); 2722 for (int k = 0; k < buttonRemove.length; ++k) { 2723 merged.remove(buttonRemove[k]); 2724 values.remove(buttonRemove[k]); 2725 widgets.remove(buttonRemove[k]); 2726 } 2727 for (Object element : button.getKeys()) { 2728 PdfName key = (PdfName)element; 2729 if (key.equals(PdfName.T) || key.equals(PdfName.RECT)) 2730 continue; 2731 if (key.equals(PdfName.FF)) 2732 values.put(key, button.get(key)); 2733 else 2734 widgets.put(key, button.get(key)); 2735 merged.put(key, button.get(key)); 2736 } 2737 return true; 2738 } 2739 2740 /** 2741 * A class representing a field position 2742 * @since 5.0.2 2743 */ 2744 public static class FieldPosition { 2745 public int page; 2746 public Rectangle position; 2747 } 2748}