问题
I have a PDF Template file that I am trying to populate with contents of "MyDocument". All the fields populate fine but the problem is that the "Calculated" fields in my PDF are not refreshed nor is the formatting set on other fields. How do I make the calculated fields refresh and formatting to work using ITextSharp? (I do not care if I get a C# or VB.NET answer)
VB.NET:
Public Shared Sub Serialize(ByVal stmt As MyDocument, ByVal file As FileInfo)
Dim reader As New PdfReader(TemplateFilePath.FullName)
Dim pdfStamper As New PdfStamper(reader, New FileStream(file.FullName, FileMode.Open))
Try
With itsDaDetailFields
.MoveFirst()
While Not .EOF
Dim pdfFieldName As String = NsT(Of String)(!PDFFieldName, Nothing)
If Not String.IsNullOrEmpty(pdfFieldName) Then
Dim value As String = NsT(Of String)(stmt.GetValueFromPDFField(pdfFieldName), Nothing)
If Not String.IsNullOrEmpty(value) Then
pdfStamper.AcroFields.SetField(pdfFieldName, value)
End If
End If
.MoveNext()
End While
End With
Finally
pdfStamper.FormFlattening = False
reader.Close()
pdfStamper.Close()
End Try
End Sub
C#:
public static void Serialize(MyDocument stmt, FileInfo file)
{
PdfReader reader = new PdfReader(TemplateFilePath.FullName);
PdfStamper pdfStamper = new PdfStamper(reader, new FileStream(file.FullName, FileMode.Open));
try {
var _with1 = itsDaDetailFields;
_with1.MoveFirst();
while (!_with1.EOF) {
string pdfFieldName = NsT<string>(_with1["PDFFieldName"], null);
if (!string.IsNullOrEmpty(pdfFieldName)) {
string value = NsT<string>(stmt.GetValueFromPDFField(pdfFieldName), null);
if (!string.IsNullOrEmpty(value)) {
pdfStamper.AcroFields.SetField(pdfFieldName, value);
}
}
_with1.MoveNext();
}
} finally {
pdfStamper.FormFlattening = false;
reader.Close();
pdfStamper.Close();
}
}
回答1:
So I figured out how to do it in .NET based on the following post using iText (the java version of ITextSharp - the procedure is just slightly different for .net). Feel free to read the following thread for a complete explanation and discussion of the same problem in iText:
http://itext-general.2136553.n4.nabble.com/Setting-acroform-value-via-iText-messes-with-acrofield-formating-td2167101.html
There are 2 ways to do it:
(1) Provide the display value like:
pdfStamper.AcroFields.SetField(pdfFieldName, value, <formatted value>)
as in:
pdfStamper.AcroFields.SetField(pdfFieldName, 1000, "1,000")
This wasn't optimal for me because I couldn't figure out programattically from my PDF file which textboxes were formatting their contents in which format. Some had slightly different formats (some had 2 decimal places, some had 0, some had many) so if you could keep track of how a textbox formats its data or if they all do the same thing then this might work. This also didn't fix the calculated fields problem, just seemed to fix the formatting problem.
(2) Provide javascript to "DIRTY" the value so it gets formatted & calculated:
My code turned into something like the following since I only needed to format numeric values but this can be expanded to handle other types (see discussion below).
Dim reader As New PdfReader(TemplateFilePath.FullName)
Dim pdfStamper As New PdfStamper(reader, New FileStream(file.FullName, FileMode.Open))
With pdfStamper.AcroFields
If IsNumeric(value) Then
Dim js As String = String.Format("var f = this.getField('{0}'); f.value = 1 * f.value;", pdfFieldName)
pdfStamper.JavaScript = js
End If
.SetField(pdfFieldName, value)
End With
reader.Close()
pdfStamper.Close()
So the trick is that you need to use JavaScript to get the value dirty, then Reader will apply the formatting. You can generalize this more and handle more value types based on the complete solution provided below (sorry it is in java but can be adapted to .net):
import java.io.IOException;
import java.util.ArrayList;
import com.lowagie.text.pdf.PRStream;
import com.lowagie.text.pdf.PdfDictionary;
import com.lowagie.text.pdf.PdfName;
import com.lowagie.text.pdf.PdfObject;
import com.lowagie.text.pdf.PdfReader;
import com.lowagie.text.pdf.PdfString;
import com.lowagie.text.pdf.AcroFields.Item;
public class AcroFieldJSScanner {
protected ArrayList<String> functions = null;
public void getFieldFunctions(Item item) throws IOException{
PdfDictionary dict;
for (int i = 0; i < item.size(); i++) {
dict = item.getMerged(i);
scanPdfDictionary(dict);
// dict = item.getWidget(i);
//
// scanPdfDictionary(dict);
}
}
protected void scanPdfDictionary(PdfDictionary dict) throws IOException{
PdfObject objJS = null;
String func = null;
objJS = dict.get(PdfName.JS);
if (dict.get(PdfName.S) != null && objJS != null && objJS.isString()){
PdfString strJS = (PdfString)objJS;
if (functions == null){
functions = new ArrayList<String>();
}
func = strJS.toString();
functions.add(func);
}else if (dict.get(PdfName.S) != null && objJS != null){
for(Object obj : dict.getKeys()){
PdfName pdfName = (PdfName)obj;
PdfObject pdfObj = dict.get(pdfName);
if (pdfObj.isIndirect()){
PdfObject pdfIndirectObject = PdfReader.getPdfObject(pdfObj);
func = new String(PdfReader.getStreamBytes((PRStream)pdfIndirectObject));
if (functions == null){
functions = new ArrayList<String>();
}
functions.add(func);
}else{
scanPdfObject(pdfObj);
}
}
}else{
for(Object obj : dict.getKeys()){
PdfName pdfName = (PdfName)obj;
PdfObject pdfObj = dict.get(pdfName);
scanPdfObject(pdfObj);
}
}
}
protected void scanPdfObject(PdfObject parentPdfObject) throws IOException{
if (parentPdfObject.isDictionary()){
scanPdfDictionary((PdfDictionary)parentPdfObject);
}else if (parentPdfObject.isIndirect()){
PdfObject pdfObject = PdfReader.getPdfObject(parentPdfObject);
scanPdfObject(pdfObject);
}
}
public ArrayList<String> getFunctions() {
return functions;
}
public String toString(){
StringBuilder sb = null;
if (getFunctions() != null){
sb = new StringBuilder();
for (int i =0; i< getFunctions().size();i++) {
sb.append(getFunctions().get(i)).append("\n");
}
}else{
return "No functions found";
}
return sb.toString();
}
}
And then if you know the javascript scripts that Adobe will call (using the above code) you know what type the data is so you can "DIRTY" the data. Here are some adobe data types and the javascript that is behind those data types:
public String getFieldFormat(Item item){
PdfDictionary aa = (PdfDictionary) item.getMerged(0).get(PdfName.AA);
if (null != aa)
{
PdfDictionary f = (PdfDictionary)PdfReader.getPdfObject(aa.get(PdfName.F));
if (null != f)
{
PdfString js = (PdfString)PdfReader.getPdfObject(f.get(PdfName.JS));
if (null != js)
{
String sScriptName = js.toString();
if (sScriptName.contains("AFNumber_Format"))
System.out.println("Format : Number");
else if (sScriptName.contains("AFDate_Format"))
System.out.println("Format : Date");
else if (sScriptName.contains("AFTime_Format"))
System.out.println("Format : Time");
else if (sScriptName.contains("AFSpecial_Format"))
System.out.println("Format : Special");
else if (sScriptName.contains("AFPercent_Format"))
System.out.println("Format : Percent");
else
System.out.println("Format : Custom");;
System.out.println("JS: ");
System.out.println(js);
}
}
}
}
回答2:
In my case, I found out that:
- Using FormFlattening prevents the javascript from updating the field
- Setting the field using SetField does not trigger the formatting.
So I had to change the javascript to actually write the complete value instead of just dirtying it. Like so:
JS &= String.Format("var f = this.getField('{0}'); f.value = '{1}';",
FieldName.Key, NewFieldValue)
I append the javascript code for each field into the string JS, and then I call pdfStamper.Javascript = JS
at the end.
来源:https://stackoverflow.com/questions/16093162/how-to-refresh-formatting-on-non-calculated-field-and-refresh-calculated-fields