com.lowagie.text.pdf.AcroFields Maven / Gradle / Ivy
/*
* Copyright 2003-2005 by Paulo Soares.
*
* The contents of this file are subject to the Mozilla Public License Version 1.1
* (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the License.
*
* The Original Code is 'iText, a free JAVA-PDF library'.
*
* The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
* the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
* All Rights Reserved.
* Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
* are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
*
* Contributor(s): all the names of the contributors are added in the source code
* where applicable.
*
* Alternatively, the contents of this file may be used under the terms of the
* LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
* provisions of LGPL are applicable instead of those above. If you wish to
* allow use of your version of this file only under the terms of the LGPL
* License and not to allow others to use your version of this file under
* the MPL, indicate your decision by deleting the provisions above and
* replace them with the notice and other provisions required by the LGPL.
* If you do not delete the provisions above, a recipient may use your version
* of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the MPL as stated above or under the terms of the GNU
* Library General Public License as published by the Free Software Foundation;
* either version 2 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
* details.
*
* If you didn't download this code from the following link, you should check if
* you aren't using an obsolete version:
* https://github.com/LibrePDF/OpenPDF
*/
package com.lowagie.text.pdf;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Element;
import com.lowagie.text.ExceptionConverter;
import com.lowagie.text.Font;
import com.lowagie.text.Image;
import com.lowagie.text.Rectangle;
import com.lowagie.text.error_messages.MessageLocalization;
import java.awt.Color;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.w3c.dom.Node;
/**
* Query and change fields in existing documents either by method calls or by FDF merging.
*
* @author Paulo Soares ([email protected])
*/
public class AcroFields {
PdfReader reader;
PdfWriter writer;
private Map fields;
private int topFirst;
private Map sigNames;
private HashMap sigTypes;
private boolean append;
public static final int DA_FONT = 0;
public static final int DA_SIZE = 1;
public static final int DA_COLOR = 2;
private final Map extensionFonts = new HashMap<>();
private XfaForm xfa;
/**
* A field type invalid or not found.
*/
public static final int FIELD_TYPE_NONE = 0;
/**
* A field type.
*/
public static final int FIELD_TYPE_PUSHBUTTON = 1;
/**
* A field type.
*/
public static final int FIELD_TYPE_CHECKBOX = 2;
/**
* A field type.
*/
public static final int FIELD_TYPE_RADIOBUTTON = 3;
/**
* A field type.
*/
public static final int FIELD_TYPE_TEXT = 4;
/**
* A field type.
*/
public static final int FIELD_TYPE_LIST = 5;
/**
* A field type.
*/
public static final int FIELD_TYPE_COMBO = 6;
/**
* A field type.
*/
public static final int FIELD_TYPE_SIGNATURE = 7;
private boolean lastWasString;
/**
* Holds value of property generateAppearances.
*/
private boolean generateAppearances = true;
private final Map localFonts = new HashMap<>();
private float extraMarginLeft;
private float extraMarginTop;
private List substitutionFonts;
AcroFields(PdfReader reader, PdfWriter writer) {
this.reader = reader;
this.writer = writer;
try {
xfa = new XfaForm(reader);
} catch (Exception e) {
throw new ExceptionConverter(e);
}
if (writer instanceof PdfStamperImp) {
append = ((PdfStamperImp) writer).isAppend();
}
fill();
}
void fill() {
fields = new HashMap<>();
PdfDictionary top = (PdfDictionary) PdfReader.getPdfObjectReleaseNullConverting(reader.getCatalog().get(PdfName.ACROFORM));
if (top == null) {
return;
}
PdfArray arrfds = (PdfArray) PdfReader.getPdfObjectRelease(top.get(PdfName.FIELDS));
if (arrfds == null || arrfds.size() == 0) {
return;
}
for (int k = 1; k <= reader.getNumberOfPages(); ++k) {
PdfDictionary page = reader.getPageNRelease(k);
Object o = PdfReader.getPdfObjectRelease(page.get(PdfName.ANNOTS), page);
PdfArray annots = (o instanceof PdfArray) ? (PdfArray) o : null;
if (annots == null) {
continue;
}
for (int j = 0; j < annots.size(); ++j) {
PdfDictionary annot = annots.getAsDict(j);
if (annot == null) {
PdfReader.releaseLastXrefPartial(annots.getAsIndirectObject(j));
continue;
}
if (!PdfName.WIDGET.equals(annot.getAsName(PdfName.SUBTYPE))) {
PdfReader.releaseLastXrefPartial(annots.getAsIndirectObject(j));
continue;
}
PdfDictionary widget = annot;
PdfDictionary dic = new PdfDictionary();
dic.putAll(annot);
String name = "";
PdfDictionary value = null;
PdfObject lastV = null;
int lastIDRNumber = -1;
PdfIndirectReference indirectObject = annots.getAsIndirectObject(j);
if (indirectObject != null) {
lastIDRNumber = indirectObject.getNumber();
}
PdfIndirectReference parentRef=null;
while (annot != null) {
dic.mergeDifferent(annot);
PdfString t = annot.getAsString(PdfName.T);
if (t != null) {
name = t.toUnicodeString() + "." + name;
}
if (lastV == null && annot.get(PdfName.V) != null) {
lastV = PdfReader.getPdfObjectRelease(annot.get(PdfName.V));
}
if (value == null && t != null) {
value = annot;
if (annot.get(PdfName.V) == null && lastV != null) {
value.put(PdfName.V, lastV);
}
}
int parentIDRNumber = -1;
PdfIndirectReference asIndirectObject = annot.getAsIndirectObject(PdfName.PARENT);
if (asIndirectObject != null) {
parentIDRNumber = asIndirectObject.getNumber();
parentRef=asIndirectObject;
}
if (parentIDRNumber != -1 && lastIDRNumber != parentIDRNumber) {
annot = annot.getAsDict(PdfName.PARENT);
lastIDRNumber = parentIDRNumber;
} else {
annot = null;
}
}
if (name.length() > 0) {
name = name.substring(0, name.length() - 1);
}
Item item = fields.get(name);
if (item == null) {
if(parentRef==null) {
//when there is no /Parent ref the item reference will be used
parentRef = indirectObject;
}
item = new Item(parentRef);
fields.put(name, item);
}
if (value == null) {
item.addValue(widget);
} else {
item.addValue(value);
}
item.addWidget(widget);
item.addWidgetRef(annots.getAsIndirectObject(j)); // must be a reference
dic.mergeDifferent(top);
item.addMerged(dic);
item.addPage(k);
item.addTabOrder(j);
}
}
// some tools produce invisible signatures without an entry in the page annotation array
// look for a single level annotation
PdfNumber sigFlags = top.getAsNumber(PdfName.SIGFLAGS);
if (sigFlags == null || (sigFlags.intValue() & 1) != 1) {
return;
}
for (int j = 0; j < arrfds.size(); ++j) {
PdfDictionary annot = arrfds.getAsDict(j);
PdfIndirectReference annotRef = arrfds.getAsIndirectObject(j);
if (annot == null) {
PdfReader.releaseLastXrefPartial(annotRef);
continue;
}
if (!PdfName.WIDGET.equals(annot.getAsName(PdfName.SUBTYPE))) {
PdfReader.releaseLastXrefPartial(arrfds.getAsIndirectObject(j));
continue;
}
PdfArray kids = (PdfArray) PdfReader.getPdfObjectRelease(annot.get(PdfName.KIDS));
if (kids != null) {
continue;
}
PdfDictionary dic = new PdfDictionary();
dic.putAll(annot);
PdfString t = annot.getAsString(PdfName.T);
if (t == null) {
continue;
}
String name = t.toUnicodeString();
if (fields.containsKey(name)) {
continue;
}
Item item = new Item(annotRef);
fields.put(name, item);
item.addValue(dic);
item.addWidget(dic);
item.addWidgetRef(arrfds.getAsIndirectObject(j)); // must be a reference
item.addMerged(dic);
item.addPage(-1);
item.addTabOrder(-1);
}
}
/**
* Gets the list of appearance names. Use it to get the names allowed with radio and checkbox fields. If the /Opt key exists the values
* will also be included. The name 'Off' may also be valid even if not returned in the list.
*
* @param fieldName the fully qualified field name
* @return the list of names or null
if the field does not exist
*/
public String[] getAppearanceStates(String fieldName) {
Item fd = fields.get(fieldName);
if (fd == null) {
return null;
}
Map names = new HashMap<>();
PdfDictionary vals = fd.getValue(0);
PdfString stringOpt = vals.getAsString(PdfName.OPT);
if (stringOpt != null) {
names.put(stringOpt.toUnicodeString(), null);
} else {
PdfArray arrayOpt = vals.getAsArray(PdfName.OPT);
if (arrayOpt != null) {
for (int k = 0; k < arrayOpt.size(); ++k) {
PdfString valStr = arrayOpt.getAsString(k);
if (valStr != null) {
names.put(valStr.toUnicodeString(), null);
}
}
}
}
for (int k = 0; k < fd.size(); ++k) {
PdfDictionary dic = fd.getWidget(k);
dic = dic.getAsDict(PdfName.AP);
if (dic == null) {
continue;
}
dic = dic.getAsDict(PdfName.N);
if (dic == null) {
continue;
}
for (Object o : dic.getKeys()) {
String name = PdfName.decodeName(o.toString());
names.put(name, null);
}
}
String[] out = new String[names.size()];
return names.keySet().toArray(out);
}
/**
* Returns the names of the N-appearance dictionaries
* @param fieldName name of the form field
* @param idx widget index
* @return String[] of appearance names or null if the field can not be found
*/
public String[] getAppearanceNames(String fieldName, int idx) {
Item fd = (Item)this.fields.get(fieldName);
if (fd == null) return null;
ArrayList names = new ArrayList();
PdfDictionary dic = fd.getWidget( idx );
dic = dic.getAsDict(PdfName.AP);
if (dic != null) {
dic = dic.getAsDict(PdfName.N);
if (dic != null) {
Iterator it = dic.getKeys().iterator();
while(it.hasNext()) {
String name = PdfName.decodeName(((PdfName)it.next()).toString());
if(!names.contains(name)){
names.add(name);
}
}
}
}
return names.toArray(new String[names.size()]);
}
private String[] getListOption(String fieldName, int idx) {
Item fd = getFieldItem(fieldName);
if (fd == null) {
return null;
}
PdfArray ar = fd.getMerged(0).getAsArray(PdfName.OPT);
if (ar == null) {
return null;
}
String[] ret = new String[ar.size()];
for (int k = 0; k < ar.size(); ++k) {
PdfObject obj = ar.getDirectObject(k);
try {
if (obj.isArray()) {
obj = ((PdfArray) obj).getDirectObject(idx);
}
if (obj.isString()) {
ret[k] = ((PdfString) obj).toUnicodeString();
} else {
ret[k] = obj.toString();
}
} catch (Exception e) {
ret[k] = "";
}
}
return ret;
}
/**
* Gets the list of export option values from fields of type list or combo. If the field doesn't exist or the field type is not list or
* combo it will return
* null
.
*
* @param fieldName the field name
* @return the list of export option values from fields of type list or combo
*/
public String[] getListOptionExport(String fieldName) {
return getListOption(fieldName, 0);
}
/**
* Gets the list of display option values from fields of type list or combo. If the field doesn't exist or the field type is not list or
* combo it will return
* null
.
*
* @param fieldName the field name
* @return the list of export option values from fields of type list or combo
*/
public String[] getListOptionDisplay(String fieldName) {
return getListOption(fieldName, 1);
}
/**
* Sets the option list for fields of type list or combo. One of exportValues
or displayValues
may be
* null
but not both. This method will only set the list but will not set the value or appearance. For that, calling
* setField()
is required.
*
* An example:
*
*
* PdfReader pdf = new PdfReader("input.pdf"); PdfStamper stp = new PdfStamper(pdf, new FileOutputStream("output.pdf")); AcroFields af =
* stp.getAcroFields(); af.setListOption("ComboBox", new String[]{"a", "b", "c"}, new String[]{"first", "second", "third"});
* af.setField("ComboBox", "b"); stp.close();
*
*
* @param fieldName the field name
* @param exportValues the export values
* @param displayValues the display values
* @return true
if the operation succeeded, false
otherwise
*/
public boolean setListOption(String fieldName, String[] exportValues, String[] displayValues) {
if (exportValues == null && displayValues == null) {
return false;
}
if (exportValues != null && displayValues != null && exportValues.length != displayValues.length) {
throw new IllegalArgumentException(
MessageLocalization.getComposedMessage("the.export.and.the.display.array.must.have.the.same.size"));
}
int ftype = getFieldType(fieldName);
if (ftype != FIELD_TYPE_COMBO && ftype != FIELD_TYPE_LIST) {
return false;
}
Item fd = fields.get(fieldName);
String[] sing = null;
if (exportValues == null) {
sing = displayValues;
} else if (displayValues == null) {
sing = exportValues;
}
PdfArray opt = new PdfArray();
if (sing != null) {
for (String s : sing) {
opt.add(new PdfString(s, PdfObject.TEXT_UNICODE));
}
} else {
for (int k = 0; k < exportValues.length; ++k) {
PdfArray a = new PdfArray();
a.add(new PdfString(exportValues[k], PdfObject.TEXT_UNICODE));
a.add(new PdfString(displayValues[k], PdfObject.TEXT_UNICODE));
opt.add(a);
}
}
fd.writeToAll(PdfName.OPT, opt, Item.WRITE_VALUE | Item.WRITE_MERGED);
return true;
}
/**
* Gets the field type. The type can be one of: FIELD_TYPE_PUSHBUTTON
,
* FIELD_TYPE_CHECKBOX
, FIELD_TYPE_RADIOBUTTON
,
* FIELD_TYPE_TEXT
, FIELD_TYPE_LIST
,
* FIELD_TYPE_COMBO
or FIELD_TYPE_SIGNATURE
.
*
* If the field does not exist or is invalid it returns
* FIELD_TYPE_NONE
.
*
* @param fieldName the field name
* @return the field type
*/
public int getFieldType(String fieldName) {
Item fd = getFieldItem(fieldName);
if (fd == null) {
return FIELD_TYPE_NONE;
}
PdfDictionary merged = fd.getMerged(0);
PdfName type = merged.getAsName(PdfName.FT);
if (type == null) {
return FIELD_TYPE_NONE;
}
int ff = 0;
PdfNumber ffo = merged.getAsNumber(PdfName.FF);
if (ffo != null) {
ff = ffo.intValue();
}
if (PdfName.BTN.equals(type)) {
if ((ff & PdfFormField.FF_PUSHBUTTON) != 0) {
return FIELD_TYPE_PUSHBUTTON;
}
if ((ff & PdfFormField.FF_RADIO) != 0) {
return FIELD_TYPE_RADIOBUTTON;
} else {
return FIELD_TYPE_CHECKBOX;
}
} else if (PdfName.TX.equals(type)) {
return FIELD_TYPE_TEXT;
} else if (PdfName.CH.equals(type)) {
if ((ff & PdfFormField.FF_COMBO) != 0) {
return FIELD_TYPE_COMBO;
} else {
return FIELD_TYPE_LIST;
}
} else if (PdfName.SIG.equals(type)) {
return FIELD_TYPE_SIGNATURE;
}
return FIELD_TYPE_NONE;
}
/**
* Export the fields as a FDF.
*
* @param writer the FDF writer
*/
public void exportAsFdf(FdfWriter writer) {
for (Map.Entry entry : fields.entrySet()) {
Item item = entry.getValue();
String name = entry.getKey();
PdfObject v = item.getMerged(0).get(PdfName.V);
if (v == null) {
continue;
}
String value = getField(name);
if (lastWasString) {
writer.setFieldAsString(name, value);
} else {
writer.setFieldAsName(name, value);
}
}
}
/**
* Renames a field. Only the last part of the name can be renamed. For example, if the original field is "ab.cd.ef" only the "ef" part can
* be renamed.
*
* @param oldName the old field name
* @param newName the new field name
* @return true
if the renaming was successful, false
* otherwise
*/
public boolean renameField(String oldName, String newName) {
int idx1 = oldName.lastIndexOf('.') + 1;
int idx2 = newName.lastIndexOf('.') + 1;
if (idx1 != idx2) {
return false;
}
if (!oldName.substring(0, idx1).equals(newName.substring(0, idx2))) {
return false;
}
if (fields.containsKey(newName)) {
return false;
}
Item item = fields.get(oldName);
if (item == null) {
return false;
}
newName = newName.substring(idx2);
PdfString ss = new PdfString(newName, PdfObject.TEXT_UNICODE);
item.writeToAll(PdfName.T, ss, Item.WRITE_VALUE | Item.WRITE_MERGED);
item.markUsed(this, Item.WRITE_VALUE);
fields.remove(oldName);
fields.put(newName, item);
return true;
}
public static Object[] splitDAelements(String da) {
try {
PRTokeniser tk = new PRTokeniser(PdfEncodings.convertToBytes(da, null));
List stack = new ArrayList<>();
Object[] ret = new Object[3];
while (tk.nextToken()) {
if (tk.getTokenType() == PRTokeniser.TK_COMMENT) {
continue;
}
if (tk.getTokenType() == PRTokeniser.TK_OTHER) {
String operator = tk.getStringValue();
switch (operator) {
case "Tf":
if (stack.size() >= 2) {
ret[DA_FONT] = stack.get(stack.size() - 2);
ret[DA_SIZE] = Float.parseFloat(stack.get(stack.size() - 1));
}
break;
case "g":
if (stack.size() >= 1) {
float gray = Float.parseFloat(stack.get(stack.size() - 1));
if (gray != 0) {
ret[DA_COLOR] = new GrayColor(gray);
}
}
break;
case "rg":
if (stack.size() >= 3) {
float red = Float.parseFloat(stack.get(stack.size() - 3));
float green = Float.parseFloat(stack.get(stack.size() - 2));
float blue = Float.parseFloat(stack.get(stack.size() - 1));
ret[DA_COLOR] = new Color(red, green, blue);
}
break;
case "k":
if (stack.size() >= 4) {
float cyan = Float.parseFloat(stack.get(stack.size() - 4));
float magenta = Float.parseFloat(stack.get(stack.size() - 3));
float yellow = Float.parseFloat(stack.get(stack.size() - 2));
float black = Float.parseFloat(stack.get(stack.size() - 1));
ret[DA_COLOR] = new CMYKColor(cyan, magenta, yellow, black);
}
break;
}
stack.clear();
} else {
stack.add(tk.getStringValue());
}
}
return ret;
} catch (IOException ioe) {
throw new ExceptionConverter(ioe);
}
}
public void decodeGenericDictionary(PdfDictionary merged, BaseField tx) throws DocumentException {
int flags = 0;
// the text size and color
PdfString da = merged.getAsString(PdfName.DA);
if (da != null) {
Object[] dab = splitDAelements(da.toUnicodeString());
if (dab[DA_SIZE] != null) {
tx.setFontSize((Float) dab[DA_SIZE]);
}
if (dab[DA_COLOR] != null) {
tx.setTextColor((Color) dab[DA_COLOR]);
}
if (dab[DA_FONT] != null) {
PdfDictionary dr = merged.getAsDict(PdfName.DR);
if (dr != null) {
PdfDictionary font = dr.getAsDict(PdfName.FONT);
if (font != null) {
PdfObject po = font.get(new PdfName((String) dab[DA_FONT]));
if (po != null && po.type() == PdfObject.INDIRECT) {
PRIndirectReference por = (PRIndirectReference) po;
adjustFontEncoding(dr, por);
BaseFont bp = new DocumentFont(por);
tx.setFont(bp);
Integer porkey = por.getNumber();
BaseFont porf = extensionFonts.get(porkey);
if (porf == null) {
if (!extensionFonts.containsKey(porkey)) {
PdfDictionary fo = (PdfDictionary) PdfReader.getPdfObject(po);
PdfDictionary fd = fo.getAsDict(PdfName.FONTDESCRIPTOR);
if (fd != null) {
PRStream prs = (PRStream) PdfReader.getPdfObject(fd.get(PdfName.FONTFILE2));
if (prs == null) {
prs = (PRStream) PdfReader.getPdfObject(fd.get(PdfName.FONTFILE3));
}
if (prs == null) {
extensionFonts.put(porkey, null);
} else {
try {
porf = BaseFont.createFont("font.ttf", BaseFont.IDENTITY_H, true, false, PdfReader.getStreamBytes(prs), null);
} catch (Exception ignored) {
}
extensionFonts.put(porkey, porf);
}
}
}
}
if (tx instanceof TextField) {
((TextField) tx).setExtensionFont(porf);
}
} else {
BaseFont bf = localFonts.get(dab[DA_FONT]);
if (bf == null) {
String[] fn = stdFieldFontNames.get(dab[DA_FONT]);
if (fn != null) {
try {
String enc = "winansi";
if (fn.length > 1) {
enc = fn[1];
}
bf = BaseFont.createFont(fn[0], enc, false);
tx.setFont(bf);
} catch (Exception e) {
// empty
}
}
} else {
tx.setFont(bf);
}
}
}
}
}
}
//rotation, border and background color
PdfDictionary mk = merged.getAsDict(PdfName.MK);
if (mk != null) {
PdfArray ar = mk.getAsArray(PdfName.BC);
Color border = getMKColor(ar);
tx.setBorderColor(border);
if (border != null) {
tx.setBorderWidth(1);
}
ar = mk.getAsArray(PdfName.BG);
tx.setBackgroundColor(getMKColor(ar));
PdfNumber rotation = mk.getAsNumber(PdfName.R);
if (rotation != null) {
tx.setRotation(rotation.intValue());
}
}
//flags
PdfNumber nfl = merged.getAsNumber(PdfName.F);
flags = 0;
tx.setVisibility(BaseField.VISIBLE_BUT_DOES_NOT_PRINT);
if (nfl != null) {
flags = nfl.intValue();
if ((flags & PdfFormField.FLAGS_PRINT) != 0 && (flags & PdfFormField.FLAGS_HIDDEN) != 0) {
tx.setVisibility(BaseField.HIDDEN);
} else if ((flags & PdfFormField.FLAGS_PRINT) != 0 && (flags & PdfFormField.FLAGS_NOVIEW) != 0) {
tx.setVisibility(BaseField.HIDDEN_BUT_PRINTABLE);
} else if ((flags & PdfFormField.FLAGS_PRINT) != 0) {
tx.setVisibility(BaseField.VISIBLE);
}
}
//multiline
nfl = merged.getAsNumber(PdfName.FF);
flags = 0;
if (nfl != null) {
flags = nfl.intValue();
}
tx.setOptions(flags);
if ((flags & PdfFormField.FF_COMB) != 0) {
PdfNumber maxLen = merged.getAsNumber(PdfName.MAXLEN);
int len = 0;
if (maxLen != null) {
len = maxLen.intValue();
}
tx.setMaxCharacterLength(len);
}
//alignment
nfl = merged.getAsNumber(PdfName.Q);
if (nfl != null) {
if (nfl.intValue() == PdfFormField.Q_CENTER) {
tx.setAlignment(Element.ALIGN_CENTER);
} else if (nfl.intValue() == PdfFormField.Q_RIGHT) {
tx.setAlignment(Element.ALIGN_RIGHT);
}
}
//border styles
PdfDictionary bs = merged.getAsDict(PdfName.BS);
if (bs != null) {
PdfNumber w = bs.getAsNumber(PdfName.W);
if (w != null) {
tx.setBorderWidth(w.floatValue());
}
PdfName s = bs.getAsName(PdfName.S);
if (PdfName.D.equals(s)) {
tx.setBorderStyle(PdfBorderDictionary.STYLE_DASHED);
} else if (PdfName.B.equals(s)) {
tx.setBorderStyle(PdfBorderDictionary.STYLE_BEVELED);
} else if (PdfName.I.equals(s)) {
tx.setBorderStyle(PdfBorderDictionary.STYLE_INSET);
} else if (PdfName.U.equals(s)) {
tx.setBorderStyle(PdfBorderDictionary.STYLE_UNDERLINE);
}
} else {
PdfArray bd = merged.getAsArray(PdfName.BORDER);
if (bd != null) {
if (bd.size() >= 3) {
tx.setBorderWidth(bd.getAsNumber(2).floatValue());
}
if (bd.size() >= 4) {
tx.setBorderStyle(PdfBorderDictionary.STYLE_DASHED);
}
}
}
}
PdfAppearance getAppearance(PdfDictionary merged, String[] values, String fieldName) throws IOException, DocumentException {
topFirst = 0;
String text = (values.length > 0) ? values[0] : null;
TextField tx = null;
if (fieldCache == null || !fieldCache.containsKey(fieldName)) {
tx = new TextField(writer, null, null);
tx.setExtraMargin(extraMarginLeft, extraMarginTop);
tx.setBorderWidth(0);
tx.setSubstitutionFontList(substitutionFonts);
decodeGenericDictionary(merged, tx);
//rect
PdfArray rect = merged.getAsArray(PdfName.RECT);
Rectangle box = PdfReader.getNormalizedRectangle(rect);
if (tx.getRotation() == 90 || tx.getRotation() == 270) {
box = box.rotate();
}
tx.setBox(box);
if (fieldCache != null) {
fieldCache.put(fieldName, tx);
}
} else {
tx = (TextField) fieldCache.get(fieldName);
tx.setWriter(writer);
}
PdfName fieldType = merged.getAsName(PdfName.FT);
if (PdfName.TX.equals(fieldType)) {
if (values.length > 0 && values[0] != null) {
tx.setText(values[0]);
}
return tx.getAppearance();
}
if (!PdfName.CH.equals(fieldType)) {
throw new DocumentException(MessageLocalization.getComposedMessage("an.appearance.was.requested.without.a.variable.text.field"));
}
PdfArray opt = merged.getAsArray(PdfName.OPT);
int flags = 0;
PdfNumber nfl = merged.getAsNumber(PdfName.FF);
if (nfl != null) {
flags = nfl.intValue();
}
if ((flags & PdfFormField.FF_COMBO) != 0 && opt == null) {
tx.setText(text);
return tx.getAppearance();
}
if (opt != null) {
String[] choices = new String[opt.size()];
String[] choicesExp = new String[opt.size()];
for (int k = 0; k < opt.size(); ++k) {
PdfObject obj = opt.getPdfObject(k);
if (obj.isString()) {
choices[k] = choicesExp[k] = ((PdfString) obj).toUnicodeString();
} else {
PdfArray a = (PdfArray) obj;
choicesExp[k] = a.getAsString(0).toUnicodeString();
choices[k] = a.getAsString(1).toUnicodeString();
}
}
if ((flags & PdfFormField.FF_COMBO) != 0) {
for (int k = 0; k < choices.length; ++k) {
if (text != null && text.equals(choicesExp[k])) {
text = choices[k];
break;
}
}
tx.setText(text);
return tx.getAppearance();
}
List indexes = new ArrayList<>();
for (int k = 0; k < choicesExp.length; ++k) {
for (String val : values) {
if (val != null && val.equals(choicesExp[k])) {
indexes.add(k);
break;
}
}
}
tx.setChoices(choices);
tx.setChoiceExports(choicesExp);
tx.setChoiceSelections(indexes);
}
PdfAppearance app = tx.getListAppearance();
topFirst = tx.getTopFirst();
return app;
}
/** Set font encoding from DR-structure if font doesn't have this info itself */
private void adjustFontEncoding(PdfDictionary dr, PRIndirectReference por) {
PdfDictionary drEncoding = dr.getAsDict(PdfName.ENCODING);
if (drEncoding != null) {
PdfDictionary fontDict = (PdfDictionary) PdfReader.getPdfObject(por);
if (fontDict != null && fontDict.get(PdfName.ENCODING) == null) {
for (PdfName key: drEncoding.getKeys()) {
fontDict.put(PdfName.ENCODING, drEncoding.get(key));
}
}
}
}
PdfAppearance getAppearance(PdfDictionary merged, String text, String fieldName) throws IOException, DocumentException {
String[] valueArr = new String[1];
valueArr[0] = text;
return getAppearance(merged, valueArr, fieldName);
}
Color getMKColor(PdfArray ar) {
if (ar == null) {
return null;
}
switch (ar.size()) {
case 1:
return new GrayColor(ar.getAsNumber(0).floatValue());
case 3:
return new Color(ExtendedColor.normalize(ar.getAsNumber(0).floatValue()), ExtendedColor.normalize(ar.getAsNumber(1).floatValue()),
ExtendedColor.normalize(ar.getAsNumber(2).floatValue()));
case 4:
return new CMYKColor(ar.getAsNumber(0).floatValue(), ar.getAsNumber(1).floatValue(), ar.getAsNumber(2).floatValue(),
ar.getAsNumber(3).floatValue());
default:
return null;
}
}
/**
* Gets the field value.
*
* @param name the fully qualified field name
* @return the field value
*/
public String getField(String name) {
if (xfa.isXfaPresent()) {
name = xfa.findFieldName(name, this);
if (name == null) {
return null;
}
name = XfaForm.Xml2Som.getShortName(name);
return XfaForm.getNodeText(xfa.findDatasetsNode(name));
}
Item item = fields.get(name);
if (item == null) {
return null;
}
lastWasString = false;
PdfDictionary mergedDict = item.getMerged(0);
// Jose A. Rodriguez posted a fix to the mailing list (May 11, 2009)
// explaining that the value can also be a stream value
// the fix was made against an old iText version. Bruno adapted it.
PdfObject v = PdfReader.getPdfObject(mergedDict.get(PdfName.V));
if (v == null) {
if(this.getFieldType(name) == AcroFields.FIELD_TYPE_CHECKBOX){
return "Off";
}
return "";
}
if (v instanceof PRStream) {
byte[] valBytes;
try {
valBytes = PdfReader.getStreamBytes((PRStream) v);
return new String(valBytes);
} catch (IOException e) {
throw new ExceptionConverter(e);
}
}
PdfName type = mergedDict.getAsName(PdfName.FT);
if (PdfName.BTN.equals(type)) {
PdfNumber ff = mergedDict.getAsNumber(PdfName.FF);
int flags = 0;
if (ff != null) {
flags = ff.intValue();
}
if ((flags & PdfFormField.FF_PUSHBUTTON) != 0) {
return "";
}
String value = "";
if (v instanceof PdfName) {
value = PdfName.decodeName(v.toString());
} else if (v instanceof PdfString) {
value = ((PdfString) v).toUnicodeString();
}
PdfArray opts = item.getValue(0).getAsArray(PdfName.OPT);
if (opts != null) {
int idx = 0;
try {
idx = Integer.parseInt(value);
PdfString ps = opts.getAsString(idx);
value = ps.toUnicodeString();
lastWasString = true;
} catch (Exception ignored) {
}
}
return value;
}
if (v instanceof PdfString) {
lastWasString = true;
return ((PdfString) v).toUnicodeString();
} else if (v instanceof PdfName) {
return PdfName.decodeName(v.toString());
}
else if (v instanceof PdfArray) {
return ((PdfArray)v).toString();
}
else {
return "";
}
}
/**
* Gets the field values of a Choice field.
*
* @param name the fully qualified field name
* @return the field value
* @since 2.1.3
*/
public String[] getListSelection(String name) {
String[] ret;
String s = getField(name);
if (s == null) {
ret = new String[]{};
} else {
ret = new String[]{s};
}
Item item = fields.get(name);
if (item == null) {
return ret;
}
//PdfName type = (PdfName)PdfReader.getPdfObject(((PdfDictionary)item.merged.get(0)).get(PdfName.FT));
//if (!PdfName.CH.equals(type)) {
// return ret;
//}
PdfArray values = item.getMerged(0).getAsArray(PdfName.I);
if (values == null) {
return ret;
}
ret = new String[values.size()];
String[] options = getListOptionExport(name);
PdfNumber n;
int idx = 0;
for (PdfObject pdfObject : values.getElements()) {
n = (PdfNumber) pdfObject;
ret[idx++] = options[n.intValue()];
}
return ret;
}
/**
* Sets a field property. Valid property names are:
*
*
* - textfont - sets the text font. The value for this entry is a
BaseFont
.
* - textcolor - sets the text color. The value for this entry is a
java.awt.Color
.
* - textsize - sets the text size. The value for this entry is a
Float
.
* - bgcolor - sets the background color. The value for this entry is a
java.awt.Color
.
* If null
removes the background.
* - bordercolor - sets the border color. The value for this entry is a
java.awt.Color
.
* If null
removes the border.
*
*
* @param field the field name
* @param name the property name
* @param value the property value
* @param inst an array of int
indexing into AcroField.Item.merged
elements to process. Set to null
* to process all
* @return true
if the property exists, false
otherwise
*/
public boolean setFieldProperty(String field, String name, Object value, int[] inst) {
if (writer == null) {
throw new RuntimeException(MessageLocalization.getComposedMessage("this.acrofields.instance.is.read.only"));
}
try {
Item item = fields.get(field);
if (item == null) {
return false;
}
InstHit hit = new InstHit(inst);
PdfDictionary merged;
PdfString da;
if (name.equalsIgnoreCase("textfont")) {
for (int k = 0; k < item.size(); ++k) {
if (hit.isHit(k)) {
merged = item.getMerged(k);
da = merged.getAsString(PdfName.DA);
PdfDictionary dr = merged.getAsDict(PdfName.DR);
if (da != null && dr != null) {
Object[] dao = splitDAelements(da.toUnicodeString());
PdfAppearance cb = new PdfAppearance(this.writer);
if (dao[DA_FONT] != null) {
BaseFont bf = (BaseFont) value;
PdfName psn = PdfAppearance.stdFieldFontNames.get(bf.getPostscriptFontName());
if (psn == null) {
psn = new PdfName(bf.getPostscriptFontName());
}
PdfDictionary fonts = dr.getAsDict(PdfName.FONT);
if (fonts == null) {
fonts = new PdfDictionary();
dr.put(PdfName.FONT, fonts);
}
PdfIndirectReference fref = (PdfIndirectReference) fonts.get(psn);
PdfDictionary top = reader.getCatalog().getAsDict(PdfName.ACROFORM);
markUsed(top);
dr = top.getAsDict(PdfName.DR);
if (dr == null) {
dr = new PdfDictionary();
top.put(PdfName.DR, dr);
}
markUsed(dr);
PdfDictionary fontsTop = dr.getAsDict(PdfName.FONT);
if (fontsTop == null) {
fontsTop = new PdfDictionary();
dr.put(PdfName.FONT, fontsTop);
}
markUsed(fontsTop);
PdfIndirectReference frefTop = (PdfIndirectReference) fontsTop.get(psn);
if (frefTop != null) {
if (fref == null) {
fonts.put(psn, frefTop);
}
} else if (fref == null) {
FontDetails fd;
if (bf.getFontType() == BaseFont.FONT_TYPE_DOCUMENT) {
fd = new FontDetails(null, ((DocumentFont) bf).getIndirectReference(), bf);
} else {
bf.setSubset(false);
fd = writer.addSimple(bf);
localFonts.put(psn.toString().substring(1), bf);
}
fontsTop.put(psn, fd.getIndirectReference());
fonts.put(psn, fd.getIndirectReference());
}
ByteBuffer buf = cb.getInternalBuffer();
buf.append(psn.getBytes()).append(' ').append((Float) dao[DA_SIZE]).append(" Tf ");
if (dao[DA_COLOR] != null) {
cb.setColorFill((Color) dao[DA_COLOR]);
}
PdfString s = new PdfString(cb.toString());
item.getMerged(k).put(PdfName.DA, s);
item.getWidget(k).put(PdfName.DA, s);
markUsed(item.getWidget(k));
}
}
}
}
} else if (name.equalsIgnoreCase("textcolor")) {
for (int k = 0; k < item.size(); ++k) {
if (hit.isHit(k)) {
merged = item.getMerged(k);
da = merged.getAsString(PdfName.DA);
if (da != null) {
Object[] dao = splitDAelements(da.toUnicodeString());
PdfAppearance cb = new PdfAppearance(this.writer);
if (dao[DA_FONT] != null) {
ByteBuffer buf = cb.getInternalBuffer();
buf.append(new PdfName((String) dao[DA_FONT]).getBytes()).append(' ').append((Float) dao[DA_SIZE])
.append(" Tf ");
cb.setColorFill((Color) value);
PdfString s = new PdfString(cb.toString());
item.getMerged(k).put(PdfName.DA, s);
item.getWidget(k).put(PdfName.DA, s);
markUsed(item.getWidget(k));
}
}
}
}
} else if (name.equalsIgnoreCase("textsize")) {
for (int k = 0; k < item.size(); ++k) {
if (hit.isHit(k)) {
merged = item.getMerged(k);
da = merged.getAsString(PdfName.DA);
if (da != null) {
Object[] dao = splitDAelements(da.toUnicodeString());
PdfAppearance cb = new PdfAppearance(this.writer);
if (dao[DA_FONT] != null) {
ByteBuffer buf = cb.getInternalBuffer();
Float fontSize = (float) Font.DEFAULTSIZE;
if (value != null) {
fontSize = (Float) value;
}
buf.append(new PdfName((String) dao[DA_FONT]).getBytes()).append(' ').append(fontSize).append(" Tf ");
if (dao[DA_COLOR] != null) {
cb.setColorFill((Color) dao[DA_COLOR]);
}
PdfString s = new PdfString(cb.toString());
item.getMerged(k).put(PdfName.DA, s);
item.getWidget(k).put(PdfName.DA, s);
markUsed(item.getWidget(k));
}
}
}
}
} else if (name.equalsIgnoreCase("bgcolor") || name.equalsIgnoreCase("bordercolor")) {
PdfName dname = (name.equalsIgnoreCase("bgcolor") ? PdfName.BG : PdfName.BC);
for (int k = 0; k < item.size(); ++k) {
if (hit.isHit(k)) {
merged = item.getMerged(k);
PdfDictionary mk = merged.getAsDict(PdfName.MK);
if (mk == null) {
if (value == null) {
return true;
}
mk = new PdfDictionary();
item.getMerged(k).put(PdfName.MK, mk);
item.getWidget(k).put(PdfName.MK, mk);
markUsed(item.getWidget(k));
} else {
markUsed(mk);
}
if (value == null) {
mk.remove(dname);
} else {
mk.put(dname, PdfFormField.getMKColor((Color) value));
}
}
}
} else {
return false;
}
return true;
} catch (Exception e) {
throw new ExceptionConverter(e);
}
}
/**
* Gets a field property. Valid property names are:
*
*
* - textfont - gets the text font. The return value for this entry is a
String
.
* - textcolor - gets the text color. The return value for this entry is a
java.awt.Color
.
* - textsize - gets the text size. The return value for this entry is a
Float
.
* - textfontsall - gets all available fonts used in the PDF. The return value for this entry is a
*
Set<String>
with font names.
* - bgcolor - gets the background color. The return value for this entry is a
java.awt.Color
.
* - bordercolor - gets the border color. The return value for this entry is a
java.awt.Color
.
*
*
* If the property does not exist null is returned.
*
* @param field - the field name
* @param name - the property name
* @param idx - the index of the widget
* @return the above described object or null if the property is not set
*/
public Object getFieldProperty(String field, String name, int idx) {
if (this.reader == null)
throw new RuntimeException("No reader has been supplied to this AcroFields instance!");
try {
Item item = (Item) this.fields.get(field);
if (item == null)
return false;
PdfDictionary merged;
PdfString da;
if (name.startsWith("text") && item.size() > idx) {
merged = item.getMerged(idx);
da = merged.getAsString(PdfName.DA);
if (da != null) {
Object[] dao = splitDAelements(da.toUnicodeString());
if ("textfont".equalsIgnoreCase(name)) {
String fontResourceName = (String) dao[DA_FONT];
if (fontResourceName == null) {
return fontResourceName;
}
// check the resource dict to get the real font name
PdfDictionary dr = merged.getAsDict(PdfName.DR);
// might have to look globally instead?
if (dr == null)
return fontResourceName;
PdfDictionary fonts = dr.getAsDict(PdfName.FONT);
if (fonts == null)
return fontResourceName;
PdfName fontResourceId = new PdfName(fontResourceName);
PdfDictionary fontDict = fonts.getAsDict(fontResourceId);
if (fontDict == null) {
// this is strange normally a font should have been found!
return fontResourceName;
}
// Do not use "PdfName.Name" since it is deprecated (according to the PDF spec)
PdfName fontName = fontDict.getAsName(PdfName.BASEFONT);
// theoretically we could check the fontdescriptor and get the /FontName but
// BASEFONT is required so it should always be there...
if (fontName == null) {
// this would be strange since BASEFONT is required according to the pdf spec)
return fontResourceName;
}
return PdfName.decodeName(fontName.toString());
} else if ("textcolor".equalsIgnoreCase(name)) {
return dao[DA_COLOR];
} else if ("textsize".equalsIgnoreCase(name)) {
return dao[DA_SIZE];
} else if ("textfontsall".equalsIgnoreCase(name)) {
HashSet fonts = new HashSet<>();
// fetch all fonts
PdfDictionary root = this.reader.getCatalog();
PdfDictionary acrofields = root.getAsDict(PdfName.ACROFORM);
PdfDictionary allFieldsDR = acrofields.getAsDict(PdfName.DR);
if (allFieldsDR == null)
return fonts;
PdfDictionary acroFieldsFonts = allFieldsDR.getAsDict(PdfName.FONT);
if (acroFieldsFonts == null)
return fonts;
for (PdfName key : acroFieldsFonts.getKeys()) {
PdfDictionary pdfFont = acroFieldsFonts.getAsDict(key);
PdfName fontName = pdfFont.getAsName(PdfName.BASEFONT);
if (fontName != null) {
fonts.add(PdfName.decodeName(fontName.toString()));
} else {
// Basefont was not found (which is unlikely since required by pdf spec. (so its a
// fallback)
PdfDictionary fontDescriptor = pdfFont.getAsDict(PdfName.FONTDESCRIPTOR);
if (fontDescriptor != null) {
// the BASEFONT was not set so extract the /FontName key
PdfString fontname = fontDescriptor.getAsString(PdfName.FONTNAME);
if (fontname != null) {
fonts.add(PdfName.decodeName(fontname.toString()));
}
}
}
}
return fonts;
}
throw new RuntimeException("Unknown property: " + name);
}
return null;
} else if ("bgcolor".equalsIgnoreCase(name) || "bordercolor".equalsIgnoreCase(name)) {
PdfName dname = ("bgcolor".equalsIgnoreCase(name) ? PdfName.BG : PdfName.BC);
merged = item.getMerged(idx);
PdfDictionary mk = merged.getAsDict(PdfName.MK);
if (mk == null) {
return null;
} else {
PdfArray color = mk.getAsArray(dname);
return parseColor(color);
}
} else {
throw new RuntimeException("Unknown property: " + name);
}
} catch (Exception e) {
throw new ExceptionConverter(e);
}
}
/**
* Parses and converts colors from PDF to standard AWT Colors.
* @param pdfColor an array of colors
* @return AWT-Color
*/
public static Color parseColor(PdfArray pdfColor){
//Check for no color -> thus transparent
if(pdfColor!=null && !pdfColor.isEmpty()){
if(pdfColor.size()==1){
PdfNumber grey = pdfColor.getAsNumber(0);
if(grey!=null) {
return new GrayColor(grey.floatValue());
}
}
else if(pdfColor.size()==3){
PdfNumber red = pdfColor.getAsNumber(0);
PdfNumber green = pdfColor.getAsNumber(1);
PdfNumber blue = pdfColor.getAsNumber(2);
if(red!=null && green!=null && blue!=null) {
return new Color(red.floatValue(), green.floatValue(), blue.floatValue());
}
}
else if(pdfColor.size()==4){
PdfNumber c = pdfColor.getAsNumber(0);
PdfNumber m = pdfColor.getAsNumber(1);
PdfNumber y = pdfColor.getAsNumber(2);
PdfNumber k = pdfColor.getAsNumber(3);
if(c!=null && m!=null && y!=null && k!=null) {
return new CMYKColor(c.floatValue(), m.floatValue(), y.floatValue(), k.floatValue());
}
}
else{
throw new RuntimeException("Error extracting color: "+pdfColor+" since the color array is too long: "+pdfColor.length());
}
}
return null;
}
/**
* Sets a field property. Valid property names are:
*
*
* - flags - a set of flags specifying various characteristics of the field's widget annotation.
* The value of this entry replaces that of the F entry in the form's corresponding annotation dictionary.
* - setflags - a set of flags to be set (turned on) in the F entry of the form's corresponding
* widget annotation dictionary. Bits equal to 1 cause the corresponding bits in F to be set to 1.
* - clrflags - a set of flags to be cleared (turned off) in the F entry of the form's corresponding
* widget annotation dictionary. Bits equal to 1 cause the corresponding bits in F to be set to 0.
* - fflags - a set of flags specifying various characteristics of the field. The value
* of this entry replaces that of the Ff entry in the form's corresponding field dictionary.
* - setfflags - a set of flags to be set (turned on) in the Ff entry of the form's corresponding
* field dictionary. Bits equal to 1 cause the corresponding bits in Ff to be set to 1.
* - clrfflags - a set of flags to be cleared (turned off) in the Ff entry of the form's corresponding
* field dictionary. Bits equal to 1 cause the corresponding bits in Ff to be set to 0.
*
*
* @param field the field name
* @param name the property name
* @param value the property value
* @param inst an array of int
indexing into AcroField.Item.merged
elements to process. Set to null
* to process all
* @return true
if the property exists, false
otherwise
*/
public boolean setFieldProperty(String field, String name, int value, int[] inst) {
return this.setFieldProperty(this.fields.get(field), name, value, inst);
}
/**
* Sets a field property. Valid property names are:
*
*
* - flags - a set of flags specifying various characteristics of the field's widget annotation.
* The value of this entry replaces that of the F entry in the form's corresponding annotation dictionary.
* - setflags - a set of flags to be set (turned on) in the F entry of the form's corresponding
* widget annotation dictionary. Bits equal to 1 cause the corresponding bits in F to be set to 1.
* - clrflags - a set of flags to be cleared (turned off) in the F entry of the form's corresponding
* widget annotation dictionary. Bits equal to 1 cause the corresponding bits in F to be set to 0.
* - fflags - a set of flags specifying various characteristics of the field. The value
* of this entry replaces that of the Ff entry in the form's corresponding field dictionary.
* - setfflags - a set of flags to be set (turned on) in the Ff entry of the form's corresponding
* field dictionary. Bits equal to 1 cause the corresponding bits in Ff to be set to 1.
* - clrfflags - a set of flags to be cleared (turned off) in the Ff entry of the form's corresponding
* field dictionary. Bits equal to 1 cause the corresponding bits in Ff to be set to 0.
*
*
* @param item of the field
* @param name the property name
* @param value the property value
* @param inst an array of int
indexing into AcroField.Item.merged
elements to process. Set to null
* to process all
* @return true
if the property exists, false
otherwise
*/
public boolean setFieldProperty(Item item, String name, int value, int[] inst) {
if (writer == null) {
throw new RuntimeException(MessageLocalization.getComposedMessage("this.acrofields.instance.is.read.only"));
}
if (item == null) {
return false;
}
InstHit hit = new InstHit(inst);
if (name.equalsIgnoreCase("flags")) {
PdfNumber num = new PdfNumber(value);
for (int k = 0; k < item.size(); ++k) {
if (hit.isHit(k)) {
item.getMerged(k).put(PdfName.F, num);
item.getWidget(k).put(PdfName.F, num);
markUsed(item.getWidget(k));
}
}
} else if (name.equalsIgnoreCase("setflags")) {
for (int k = 0; k < item.size(); ++k) {
if (hit.isHit(k)) {
PdfNumber num = item.getWidget(k).getAsNumber(PdfName.F);
int val = 0;
if (num != null) {
val = num.intValue();
}
num = new PdfNumber(val | value);
item.getMerged(k).put(PdfName.F, num);
item.getWidget(k).put(PdfName.F, num);
markUsed(item.getWidget(k));
}
}
} else if (name.equalsIgnoreCase("clrflags")) {
for (int k = 0; k < item.size(); ++k) {
if (hit.isHit(k)) {
PdfDictionary widget = item.getWidget(k);
PdfNumber num = widget.getAsNumber(PdfName.F);
int val = 0;
if (num != null) {
val = num.intValue();
}
num = new PdfNumber(val & (~value));
item.getMerged(k).put(PdfName.F, num);
widget.put(PdfName.F, num);
markUsed(widget);
}
}
} else if (name.equalsIgnoreCase("fflags")) {
PdfNumber num = new PdfNumber(value);
for (int k = 0; k < item.size(); ++k) {
if (hit.isHit(k)) {
item.getMerged(k).put(PdfName.FF, num);
item.getValue(k).put(PdfName.FF, num);
markUsed(item.getValue(k));
}
}
} else if (name.equalsIgnoreCase("setfflags")) {
for (int k = 0; k < item.size(); ++k) {
if (hit.isHit(k)) {
PdfDictionary valDict = item.getValue(k);
PdfNumber num = valDict.getAsNumber(PdfName.FF);
int val = 0;
if (num != null) {
val = num.intValue();
}
num = new PdfNumber(val | value);
item.getMerged(k).put(PdfName.FF, num);
valDict.put(PdfName.FF, num);
markUsed(valDict);
}
}
} else if (name.equalsIgnoreCase("clrfflags")) {
for (int k = 0; k < item.size(); ++k) {
if (hit.isHit(k)) {
PdfDictionary valDict = item.getValue(k);
PdfNumber num = valDict.getAsNumber(PdfName.FF);
int val = 0;
if (num != null) {
val = num.intValue();
}
num = new PdfNumber(val & (~value));
item.getMerged(k).put(PdfName.FF, num);
valDict.put(PdfName.FF, num);
markUsed(valDict);
}
}
} else {
return false;
}
return true;
}
/**
* Merges an XML data structure into this form.
*
* @param n the top node of the data structure
* @throws java.io.IOException on error
* @throws com.lowagie.text.DocumentException o error
*/
public void mergeXfaData(Node n) throws IOException, DocumentException {
XfaForm.Xml2SomDatasets data = new XfaForm.Xml2SomDatasets(n);
for (String name : data.getNamesOrder()) {
String text = XfaForm.getNodeText(data.getNodesByName().get(name));
setField(name, text);
}
}
/**
* Sets the fields by FDF merging.
*
* @param fdf the FDF form
* @throws IOException on error
* @throws DocumentException on error
*/
public void setFields(FdfReader fdf) throws IOException, DocumentException {
Map fd = fdf.getAllFields();
for (String f : fd.keySet()) {
String v = fdf.getFieldValue(f);
if (v != null) {
setField(f, v);
}
}
}
/**
* Allows merging the fields by a field reader. One use would be to set the fields by XFDF merging.
*
* @param fieldReader The fields to merge.
* @throws IOException on error
* @throws DocumentException on error
*/
public void setFields(XfdfReader fieldReader) throws IOException, DocumentException {
setFields((FieldReader) fieldReader);
}
/**
* Allows merging the fields by a field reader. One use would be to set the fields by XFDF merging.
*
* @param fieldReader The fields to merge.
* @throws IOException on error
* @throws DocumentException on error
*/
public void setFields(FieldReader fieldReader) throws IOException, DocumentException {
Map fd = fieldReader.getAllFields();
for (String f : fd.keySet()) {
String v = fieldReader.getFieldValue(f);
if (v != null) {
setField(f, v);
}
List list = fieldReader.getListValues(f);
if (list != null) {
setListSelection(v, list.toArray(new String[0]));
}
}
}
/**
* Regenerates the field appearance. This is useful when you change a field property, but not its value, for instance
* form.setFieldProperty("f", "bgcolor", Color.BLUE, null); This won't have any effect, unless you use regenerateField("f") after changing
* the property.
*
* @param name the fully qualified field name or the partial name in the case of XFA forms
* @return true
if the field was found and changed,
* false
otherwise
* @throws IOException on error
* @throws DocumentException on error
*/
public boolean regenerateField(String name) throws IOException, DocumentException {
String value = getField(name);
return setField(name, value, value);
}
/**
* Sets the field value.
*
* @param name the fully qualified field name or the partial name in the case of XFA forms
* @param value the field value
* @return true
if the field was found and changed,
* false
otherwise
* @throws IOException on error
* @throws DocumentException on error
*/
public boolean setField(String name, String value) throws IOException, DocumentException {
return setField(name, value, null);
}
/**
* Sets the field value and the display string. The display string is used to build the appearance in the cases where the value is
* modified by Acrobat with JavaScript and the algorithm is known.
*
* @param name the fully qualified field name or the partial name in the case of XFA forms
* @param value the field value
* @param display the string that is used for the appearance. If null
the value
parameter will be used
* @return true
if the field was found and changed,
* false
otherwise
* @throws IOException on error
* @throws DocumentException on error
*/
public boolean setField(String name, String value, String display) throws IOException, DocumentException {
if (writer == null) {
throw new DocumentException(MessageLocalization.getComposedMessage("this.acrofields.instance.is.read.only"));
}
if (xfa.isXfaPresent()) {
name = xfa.findFieldName(name, this);
if (name == null) {
return false;
}
String shortName = XfaForm.Xml2Som.getShortName(name);
Node xn = xfa.findDatasetsNode(shortName);
if (xn == null) {
xn = xfa.getDatasetsSom().insertNode(xfa.getDatasetsNode(), shortName);
}
xfa.setNodeText(xn, value);
}
Item item = fields.get(name);
if (item == null) {
return false;
}
PdfDictionary merged = item.getMerged(0);
PdfName type = merged.getAsName(PdfName.FT);
if (PdfName.TX.equals(type)) {
PdfNumber maxLen = merged.getAsNumber(PdfName.MAXLEN);
int len = 0;
if (maxLen != null) {
len = maxLen.intValue();
}
if (len > 0) {
value = value.substring(0, Math.min(len, value.length()));
}
}
if (display == null) {
display = value;
}
if (PdfName.TX.equals(type) || PdfName.CH.equals(type)) {
PdfString v = new PdfString(value, PdfObject.TEXT_UNICODE);
for (int idx = 0; idx < item.size(); ++idx) {
PdfDictionary valueDic = item.getValue(idx);
valueDic.put(PdfName.V, v);
valueDic.remove(PdfName.I);
markUsed(valueDic);
merged = item.getMerged(idx);
merged.remove(PdfName.I);
merged.put(PdfName.V, v);
PdfDictionary widget = item.getWidget(idx);
if (generateAppearances) {
PdfAppearance app = getAppearance(merged, display, name);
if (PdfName.CH.equals(type)) {
PdfNumber n = new PdfNumber(topFirst);
widget.put(PdfName.TI, n);
merged.put(PdfName.TI, n);
}
PdfDictionary appDic = widget.getAsDict(PdfName.AP);
if (appDic == null) {
appDic = new PdfDictionary();
widget.put(PdfName.AP, appDic);
merged.put(PdfName.AP, appDic);
}
appDic.put(PdfName.N, app.getIndirectReference());
writer.releaseTemplate(app);
} else {
widget.remove(PdfName.AP);
merged.remove(PdfName.AP);
}
markUsed(widget);
}
return true;
} else if (PdfName.BTN.equals(type)) {
PdfNumber ff = item.getMerged(0).getAsNumber(PdfName.FF);
int flags = 0;
if (ff != null) {
flags = ff.intValue();
}
if ((flags & PdfFormField.FF_PUSHBUTTON) != 0) {
//we'll assume that the value is an image in base64
Image img;
try {
img = Image.getInstance(Base64.getDecoder().decode(value));
} catch (Exception e) {
return false;
}
PushbuttonField pb = getNewPushbuttonFromField(name);
pb.setImage(img);
replacePushbuttonField(name, pb.getField());
return true;
}
PdfName v = new PdfName(value);
List lopt = new ArrayList<>();
PdfArray opts = item.getValue(0).getAsArray(PdfName.OPT);
if (opts != null) {
for (int k = 0; k < opts.size(); ++k) {
PdfString valStr = opts.getAsString(k);
if (valStr != null) {
lopt.add(valStr.toUnicodeString());
} else {
lopt.add(null);
}
}
}
int vidx = lopt.indexOf(value);
PdfName vt;
if (vidx >= 0) {
vt = new PdfName(String.valueOf(vidx));
} else {
vt = v;
}
for (int idx = 0; idx < item.size(); ++idx) {
merged = item.getMerged(idx);
PdfDictionary widget = item.getWidget(idx);
PdfDictionary valDict = item.getValue(idx);
markUsed(item.getValue(idx));
valDict.put(PdfName.V, vt);
merged.put(PdfName.V, vt);
markUsed(widget);
if (isInAP(widget, vt)) {
merged.put(PdfName.AS, vt);
widget.put(PdfName.AS, vt);
} else {
merged.put(PdfName.AS, PdfName.Off);
widget.put(PdfName.AS, PdfName.Off);
}
}
return true;
}
return false;
}
/**
* Sets different values in a list selection. No appearance is generated yet; nor does the code check if multiple select is allowed.
*
* @param name the name of the field
* @param value an array with values that need to be selected
* @return true only if the field value was changed
* @throws IOException on error
* @throws DocumentException on error
* @since 2.1.4
*/
public boolean setListSelection(String name, String[] value) throws IOException, DocumentException {
Item item = getFieldItem(name);
if (item == null) {
return false;
}
PdfDictionary merged = item.getMerged(0);
PdfName type = merged.getAsName(PdfName.FT);
if (!PdfName.CH.equals(type)) {
return false;
}
String[] options = getListOptionExport(name);
PdfArray array = new PdfArray();
for (String s1 : value) {
for (int j = 0; j < options.length; j++) {
if (options[j].equals(s1)) {
array.add(new PdfNumber(j));
break;
}
}
}
item.writeToAll(PdfName.I, array, Item.WRITE_MERGED | Item.WRITE_VALUE);
PdfArray vals = new PdfArray();
for (String s : value) {
vals.add(new PdfString(s));
}
item.writeToAll(PdfName.V, vals, Item.WRITE_MERGED | Item.WRITE_VALUE);
PdfAppearance app = getAppearance(merged, value, name);
PdfDictionary apDic = new PdfDictionary();
apDic.put(PdfName.N, app.getIndirectReference());
item.writeToAll(PdfName.AP, apDic, Item.WRITE_MERGED | Item.WRITE_WIDGET);
writer.releaseTemplate(app);
item.markUsed(this, Item.WRITE_VALUE | Item.WRITE_WIDGET);
return true;
}
boolean isInAP(PdfDictionary dic, PdfName check) {
PdfDictionary appDic = dic.getAsDict(PdfName.AP);
if (appDic == null) {
return false;
}
PdfDictionary NDic = appDic.getAsDict(PdfName.N);
return (NDic != null && NDic.get(check) != null);
}
/**
* Gets all the fields. The fields are keyed by the fully qualified field name and the value is an instance of
* AcroFields.Item
.
*
* @deprecated use {@link AcroFields#getAllFields()}
*
* @return all the fields
*/
@Deprecated
public HashMap getFields() {
return (HashMap) fields;
}
/**
* Gets all the fields. The fields are keyed by the fully qualified field name and the value is an instance of
* AcroFields.Item
.
*
* @return all the fields
*/
public Map getAllFields() {
return fields;
}
/**
* Gets the field structure.
*
* @param name the name of the field
* @return the field structure or null
if the field does not exist
*/
public Item getFieldItem(String name) {
if (xfa.isXfaPresent()) {
name = xfa.findFieldName(name, this);
if (name == null) {
return null;
}
}
return fields.get(name);
}
/**
* Gets the long XFA translated name.
*
* @param name the name of the field
* @return the long field name
*/
public String getTranslatedFieldName(String name) {
if (xfa.isXfaPresent()) {
String namex = xfa.findFieldName(name, this);
if (namex != null) {
name = namex;
}
}
return name;
}
/**
* Gets the field box positions in the document. The return is an array of float
multiple of 5. For each of this groups the
* values are: [page, llx, lly, urx, ury]. The coordinates have the page rotation in consideration.
*
* @param name the field name
* @return the positions or null
if field does not exist
*/
public float[] getFieldPositions(String name) {
Item item = getFieldItem(name);
if (item == null) {
return null;
}
float[] ret = new float[item.size() * 5];
int ptr = 0;
for (int k = 0; k < item.size(); ++k) {
try {
PdfDictionary wd = item.getWidget(k);
PdfArray rect = wd.getAsArray(PdfName.RECT);
if (rect == null) {
continue;
}
Rectangle r = PdfReader.getNormalizedRectangle(rect);
int page = item.getPage(k);
int rotation = reader.getPageRotation(page);
ret[ptr++] = page;
if (rotation != 0) {
Rectangle pageSize = reader.getPageSize(page);
switch (rotation) {
case 270:
r = new Rectangle(
pageSize.getTop() - r.getBottom(),
r.getLeft(),
pageSize.getTop() - r.getTop(),
r.getRight());
break;
case 180:
r = new Rectangle(
pageSize.getRight() - r.getLeft(),
pageSize.getTop() - r.getBottom(),
pageSize.getRight() - r.getRight(),
pageSize.getTop() - r.getTop());
break;
case 90:
r = new Rectangle(
r.getBottom(),
pageSize.getRight() - r.getLeft(),
r.getTop(),
pageSize.getRight() - r.getRight());
break;
}
r.normalize();
}
ret[ptr++] = r.getLeft();
ret[ptr++] = r.getBottom();
ret[ptr++] = r.getRight();
ret[ptr++] = r.getTop();
} catch (Exception e) {
// empty on purpose
}
}
if (ptr < ret.length) {
float[] ret2 = new float[ptr];
System.arraycopy(ret, 0, ret2, 0, ptr);
return ret2;
}
return ret;
}
private int removeRefFromArray(PdfArray array, PdfObject refo) {
if (refo == null || !refo.isIndirect()) {
return array.size();
}
PdfIndirectReference ref = (PdfIndirectReference) refo;
for (int j = 0; j < array.size(); ++j) {
PdfObject obj = array.getPdfObject(j);
if (!obj.isIndirect()) {
continue;
}
if (((PdfIndirectReference) obj).getNumber() == ref.getNumber()) {
array.remove(j--);
}
}
return array.size();
}
/**
* Removes all the fields from page
.
*
* @param page the page to remove the fields from
* @return true
if any field was removed, false otherwise
*/
public boolean removeFieldsFromPage(int page) {
if (page < 1) {
return false;
}
String[] names = new String[fields.size()];
fields.keySet().toArray(names);
boolean found = false;
for (String name : names) {
boolean fr = removeField(name, page);
found = (found || fr);
}
return found;
}
/**
* Removes a field from the document. If page equals -1 all the fields with this
* name
are removed from the document otherwise only the fields in
* that particular page are removed.
*
* @param name the field name
* @param page the page to remove the field from or -1 to remove it from all the pages
* @return true
if the field exists, false otherwise
*/
public boolean removeField(String name, int page) {
Item item = getFieldItem(name);
if (item == null) {
return false;
}
PdfDictionary acroForm = (PdfDictionary) PdfReader.getPdfObject(reader.getCatalog().get(PdfName.ACROFORM), reader.getCatalog());
if (acroForm == null) {
return false;
}
PdfArray arrayf = acroForm.getAsArray(PdfName.FIELDS);
if (arrayf == null) {
return false;
}
for (int k = 0; k < item.size(); ++k) {
int pageV = item.getPage(k);
if (page != -1 && page != pageV) {
continue;
}
PdfIndirectReference ref = item.getWidgetRef(k);
PdfDictionary wd = item.getWidget(k);
PdfDictionary pageDic = reader.getPageN(pageV);
PdfArray annots = pageDic.getAsArray(PdfName.ANNOTS);
if (annots != null) {
if (removeRefFromArray(annots, ref) == 0) {
pageDic.remove(PdfName.ANNOTS);
markUsed(pageDic);
} else {
markUsed(annots);
}
}
PdfReader.killIndirect(ref);
PdfIndirectReference kid = ref;
while ((ref = wd.getAsIndirectObject(PdfName.PARENT)) != null) {
wd = wd.getAsDict(PdfName.PARENT);
PdfArray kids = wd.getAsArray(PdfName.KIDS);
if (removeRefFromArray(kids, kid) != 0) {
break;
}
kid = ref;
PdfReader.killIndirect(ref);
}
if (ref == null) {
removeRefFromArray(arrayf, kid);
markUsed(arrayf);
}
if (page != -1) {
item.remove(k);
--k;
}
}
if (page == -1 || item.size() == 0) {
fields.remove(name);
}
return true;
}
/**
* Removes a field from the document.
*
* @param name the field name
* @return true
if the field exists, false otherwise
*/
public boolean removeField(String name) {
return removeField(name, -1);
}
/**
* Gets the property generateAppearances.
*
* @return the property generateAppearances
*/
public boolean isGenerateAppearances() {
return generateAppearances;
}
/**
* Sets the option to generate appearances. Not generating appearances will speed-up form filling but the results can be unexpected in
* Acrobat. Don't use it unless your environment is well controlled. The default is true
.
*
* @param generateAppearances the option to generate appearances
*/
public void setGenerateAppearances(boolean generateAppearances) {
this.generateAppearances = generateAppearances;
PdfDictionary top = reader.getCatalog().getAsDict(PdfName.ACROFORM);
if (generateAppearances) {
top.remove(PdfName.NEEDAPPEARANCES);
} else {
top.put(PdfName.NEEDAPPEARANCES, PdfBoolean.PDFTRUE);
}
}
/**
* The field representations for retrieval and modification.
*/
public static class Item {
/**
* writeToAll
constant.
*
* @since 2.1.5
*/
public static final int WRITE_MERGED = 1;
/**
* writeToAll
and markUsed
constant.
*
* @since 2.1.5
*/
public static final int WRITE_WIDGET = 2;
/**
* writeToAll
and markUsed
constant.
*
* @since 2.1.5
*/
public static final int WRITE_VALUE = 4;
public Item(PdfIndirectReference ref) {
this.fieldReference=ref;
}
/**
* This function writes the given key/value pair to all the instances of merged, widget, and/or value, depending on the
* writeFlags
setting
*
* @param key you'll never guess what this is for.
* @param value if value is null, the key will be removed
* @param writeFlags ORed together WRITE_* flags
* @since 2.1.5
*/
public void writeToAll(PdfName key, PdfObject value, int writeFlags) {
int i;
PdfDictionary curDict = null;
if ((writeFlags & WRITE_MERGED) != 0) {
for (i = 0; i < merged.size(); ++i) {
curDict = getMerged(i);
curDict.put(key, value);
}
}
if ((writeFlags & WRITE_WIDGET) != 0) {
for (i = 0; i < widgets.size(); ++i) {
curDict = getWidget(i);
curDict.put(key, value);
}
}
if ((writeFlags & WRITE_VALUE) != 0) {
for (i = 0; i < values.size(); ++i) {
curDict = getValue(i);
curDict.put(key, value);
}
}
}
/**
* Mark all the item dictionaries used matching the given flags
*
* @param writeFlags WRITE_MERGED is ignored
* @param parentFields parent fields
* @since 2.1.5
*/
public void markUsed(AcroFields parentFields, int writeFlags) {
if ((writeFlags & WRITE_VALUE) != 0) {
for (int i = 0; i < size(); ++i) {
parentFields.markUsed(getValue(i));
}
}
if ((writeFlags & WRITE_WIDGET) != 0) {
for (int i = 0; i < size(); ++i) {
parentFields.markUsed(getWidget(i));
}
}
}
/**
* An array of PdfDictionary
where the value tag /V is present.
*
* @deprecated (will remove ' public ' in the future)
*/
public ArrayList values = new ArrayList<>();
/**
* An array of PdfDictionary
with the widgets.
*
* @deprecated (will remove ' public ' in the future)
*/
public ArrayList widgets = new ArrayList<>();
/**
* An array of PdfDictionary
with the widget references.
*
* @deprecated (will remove ' public ' in the future)
*/
public ArrayList widgetRefs = new ArrayList<>();
/**
* An array of PdfDictionary
with all the field and widget tags merged.
*
* @deprecated (will remove ' public ' in the future)
*/
public ArrayList merged = new ArrayList<>();
/**
* An array of Integer
with the page numbers where the widgets are displayed.
*
* @deprecated (will remove ' public ' in the future)
*/
public ArrayList page = new ArrayList<>();
/**
* An array of Integer
with the tab order of the field in the page.
*
* @deprecated (will remove ' public ' in the future)
*/
public ArrayList tabOrder = new ArrayList<>();
/**
* The indirect reference of the item itself
*/
private PdfIndirectReference fieldReference;
/**
* Preferred method of determining the number of instances of a given field.
*
* @return number of instances
* @since 2.1.5
*/
public int size() {
return values.size();
}
/**
* Remove the given instance from this item. It is possible to remove all instances using this function.
*
* @since 2.1.5
*/
public void remove(int killIdx) {
values.remove(killIdx);
widgets.remove(killIdx);
widgetRefs.remove(killIdx);
merged.remove(killIdx);
page.remove(killIdx);
tabOrder.remove(killIdx);
}
/**
* Retrieve the value dictionary of the given instance
*
* @param idx instance index
* @return dictionary storing this instance's value. It may be shared across instances.
* @since 2.1.5
*/
public PdfDictionary getValue(int idx) {
return values.get(idx);
}
/**
* Add a value dict to this Item
*
* @param value new value dictionary
* @since 2.1.5
*/
void addValue(PdfDictionary value) {
values.add(value);
}
/**
* Retrieve the widget dictionary of the given instance
*
* @param idx instance index
* @return The dictionary found in the appropriate page's Annot array.
* @since 2.1.5
*/
public PdfDictionary getWidget(int idx) {
return widgets.get(idx);
}
/**
* Add a widget dict to this Item
*
* @since 2.1.5
*/
void addWidget(PdfDictionary widget) {
widgets.add(widget);
}
/**
* Retrieve the reference to the given instance
*
* @param idx instance index
* @return reference to the given field instance
* @since 2.1.5
*/
public PdfIndirectReference getWidgetRef(int idx) {
return widgetRefs.get(idx);
}
/**
* Add a widget ref to this Item
*
* @since 2.1.5
*/
void addWidgetRef(PdfIndirectReference widgRef) {
widgetRefs.add(widgRef);
}
/**
* Retrieve the merged dictionary for the given instance. The merged dictionary contains all the keys present in parent fields, though
* they may have been overwritten (or modified?) by children. Example: a merged radio field dict will contain /V
*
* @param idx instance index
* @return the merged dictionary for the given instance
* @since 2.1.5
*/
public PdfDictionary getMerged(int idx) {
return merged.get(idx);
}
/**
* Adds a merged dictionary to this Item.
*
* @since 2.1.5
*/
void addMerged(PdfDictionary mergeDict) {
merged.add(mergeDict);
}
/**
* Retrieve the page number of the given instance
*
* @param idx index
* @return remember, pages are "1-indexed", not "0-indexed" like field instances.
* @since 2.1.5
*/
public Integer getPage(int idx) {
return page.get(idx);
}
/**
* Adds a page to the current Item.
*
* @since 2.1.5
*/
void addPage(int pg) {
page.add(pg);
}
/**
* forces a page value into the Item.
*
* @since 2.1.5
*/
void forcePage(int idx, int pg) {
page.set(idx, pg);
}
/**
* Gets the tabOrder.
*
* @param idx index
* @return tab index of the given field instance
* @since 2.1.5
*/
public Integer getTabOrder(int idx) {
return tabOrder.get(idx);
}
/**
* Adds a tab order value to this Item.
*
* @param order order for the tab
* @since 2.1.5
*/
void addTabOrder(int order) {
tabOrder.add(order);
}
/**
* Returns the indirect reference of the field itself
* @return PdfIndirectReferenceof the field
*/
public PdfIndirectReference getFieldReference() {
return this.fieldReference;
}
}
private static class InstHit {
IntHashtable hits;
public InstHit(int[] inst) {
if (inst == null) {
return;
}
hits = new IntHashtable();
for (int i : inst) {
hits.put(i, 1);
}
}
public boolean isHit(int n) {
if (hits == null) {
return true;
}
return hits.containsKey(n);
}
}
/**
* Gets the field names that have signatures and are signed.
*
* @deprecated user {@link AcroFields#getSignedFieldNames()}
*
* @return the field names that have signatures and are signed
*/
@Deprecated
@SuppressWarnings("unchecked")
public ArrayList getSignatureNames() {
return (ArrayList) getSignedFieldNames();
}
/**
* Gets the field names that have signatures and are signed.
*
* @return the field names that have signatures and are signed
*/
public List getSignedFieldNames() {
if (sigNames != null) {
return new ArrayList<>(sigNames.keySet());
}
sigNames = new HashMap<>();
List