How to fill XFA from using iText so it is Foxit Reader comptible - forms

I used examples available on web to create an application that is able to get xml structure of XFA form and then set it back filled. Important code looks like this:
public void readData(String src, String dest)
throws IOException, ParserConfigurationException, SAXException,
TransformerFactoryConfigurationError, TransformerException {
FileOutputStream os = new FileOutputStream(dest);
PdfReader reader = new PdfReader(src);
XfaForm xfa = new XfaForm(reader);
Node node = xfa.getDatasetsNode();
NodeList list = node.getChildNodes();
for (int i = 0; i < list.getLength(); i++) {
if ("data".equals(list.item(i).getLocalName())) {
node = list.item(i);
break;
}
}
Transformer tf = TransformerFactory.newInstance().newTransformer();
tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
tf.setOutputProperty(OutputKeys.INDENT, "yes");
tf.transform(new DOMSource(node), new StreamResult(os));
reader.close();
}
public void fillPdfWithXmlData(String src, String xml, String dest)
throws IOException, DocumentException {
PdfReader.unethicalreading = true;
PdfReader reader = new PdfReader(src);
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(dest), '\0', true);
AcroFields form = stamper.getAcroFields();
XfaForm xfa = form.getXfa();
xfa.fillXfaForm(new FileInputStream(xml));
stamper.close();
reader.close();
}
When I use it to fill this form: http://www.vzp.cz/uploads/document/tiskopisy-pro-zamestnavatele-hromadne-oznameni-zamestnavatele-verze-2-pdf-56-kb.pdf it works fine and I can see the filled form in Acrobat Reader. However, if I open the document in Foxit Reader I see a blank form (tested in latest version and 5.x version).
I tried to play a little bit with it and got these XfaForm(...).getDomDocument() data:
Filled by Acrobat Reader: http://pastebin.com/kXKyh9EM
Filled by Foxit Reader: http://pastebin.com/tiZ7EmfE
Filled by iText: http://pastebin.com/tTKLMERC
Filled by Foxit Reader after a fill by iText: http://pastebin.com/Uuq0jS4b
The field which was filled is . Is it possible to use iText in a way that it works even with Foxit Reader (and the XFA signature stays?)

In your Filled by iText example there's a superfluous <xfa:data> element:
<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
<xfa:data>
<xfa:data>
<HOZ>
<!-- rest of your data here -->
</HOZ>
</xfa:data>
</xfa:data>
<!-- data description -->
</xfa:datasets>
This is because the fillXfaForm() method of XfaForm expects the XML data without <xfa:data> as the root element. So your XML data should just look like:
<HOZ>
<!-- rest of your data here -->
</HOZ>
I see that your readData() method that extract the existing form data including the <xfa:data> element:
<xfa:data>
<HOZ>
<!-- rest of your data here -->
</HOZ>
</xfa:data>
Stripping the outer element should fix your problem. For example:
XfaForm xfa = new XfaForm(reader);
Node node = xfa.getDatasetsNode();
NodeList list = node.getChildNodes();
for (int i = 0; i < list.getLength(); i++) {
if ("data".equals(list.item(i).getLocalName())) {
node = list.item(i);
break;
}
}
// strip <xfa:data>
node = node.getFirstChild();
// Transformer code here

Related

iText7: com.itextpdf.kernel.PdfException: Dictionary doesn't have supported font data

I try to generate a toc(table of content) for my pdf, and I want to get some strings which look like chapter title in xxx.pdf using ITextExtractionStrategy. But I got com.itextpdf.kernel.PdfException when I am running a test.
Here is my code:
#org.junit.Test
public void test() throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PdfDocument pdfDoc = new PdfDocument(new PdfReader("src/test/resources/template/xxx.pdf"),
new PdfWriter(baos));
pdfDoc.addNewPage(1);
Document document = new Document(pdfDoc);
// when add this code, throw com.itextpdf.kernel.PdfException: Dictionary doesn't have supported font data.
Paragraph title = new Paragraph(new Text("index"))
.setTextAlignment(TextAlignment.CENTER);
document.add(title);
SimpleTextExtractionStrategy extractionStrategy = new SimpleTextExtractionStrategy();
for (int i = 1; i < pdfDoc.getNumberOfPages(); i++) {
PdfPage page = pdfDoc.getPage(i);
PdfCanvasProcessor parser = new PdfCanvasProcessor(extractionStrategy);
parser.processPageContent(page);
}
...
document.close();
pdfDoc.close();
new FileOutputStream("./yyy.pdf").write(baos.toByteArray());
}
Here is the output:
com.itextpdf.kernel.PdfException: Dictionary doesn't have supported font data.
at com.itextpdf.kernel.font.PdfFontFactory.createFont(PdfFontFactory.java:123)
at com.itextpdf.kernel.pdf.canvas.parser.PdfCanvasProcessor.getFont(PdfCanvasProcessor.java:490)
at com.itextpdf.kernel.pdf.canvas.parser.PdfCanvasProcessor$SetTextFontOperator.invoke(PdfCanvasProcessor.java:811)
at com.itextpdf.kernel.pdf.canvas.parser.PdfCanvasProcessor.invokeOperator(PdfCanvasProcessor.java:454)
at com.itextpdf.kernel.pdf.canvas.parser.PdfCanvasProcessor.processContent(PdfCanvasProcessor.java:282)
at com.itextpdf.kernel.pdf.canvas.parser.PdfCanvasProcessor.processPageContent(PdfCanvasProcessor.java:303)
at com.example.pdf.util.Test.test(Test.java:138)
Whenever you add content to a PdfDocument like you do here
Document document = new Document(pdfDoc);
Paragraph title = new Paragraph(new Text("index"))
.setTextAlignment(TextAlignment.CENTER);
document.add(title);
you have to be aware that this content is not already stored in its final form; for example fonts used are not yet properly subset'ed. The final form is generated when you're closing the document.
Text extraction on the other hand requires the content to extract to be in its final form.
Thus, you should not apply text extraction to a document you're working on. In particular, don't apply text extraction to a page you've changed the content of.
If you need to extract text from the documents you create yourself, close your document first, open a new document from the output, and extract from that new document.

How to use itext to submit PDF Form

After reading many stackoverflow and tried many solutions I'm stuck with this:
I am receiving a PDF that I cannot change and need to automatically process it.
The PDF is a PDF form with 2 fields and submit button.
The following code is the closes I came to what I need to do:
public static final String SRC = "C:\\Dev\\test.pdf";
public static final String DEST = "C:\\Dev\\test_result.pdf";
public static final String DATA = "C:\\Dev\\data.xml";
File file = new File(DEST);
PdfReader reader = new PdfReader(SRC);
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(DEST));
AcroFields form = stamper.getAcroFields();
XfaForm xfa = form.getXfa();
xfa.fillXfaForm(new FileInputStream(DATA));
This gives a null pointer:
Exception in thread "main" java.lang.NullPointerException
at com.itextpdf.text.pdf.XfaForm.fillXfaForm(XfaForm.java:1168)
at com.itextpdf.text.pdf.XfaForm.fillXfaForm(XfaForm.java:1146)
at com.itextpdf.text.pdf.XfaForm.fillXfaForm(XfaForm.java:1134)
at com.itextpdf.text.pdf.XfaForm.fillXfaForm(XfaForm.java:1131)
I can get and set the fields on the form with this code:
AcroFields fields = reader.getAcroFields();
fields.setField("pdfForm.loginUser", "myemail#domain.com");
fields.setField("pdfForm.loginPass", "mypassword");
How do I convert the Acrofields to XfaForm?

iText7: Error at file pointer when merging two pdfs

We are in the last steps of evaluating iText7. We use iText 7.1.0 and html2pdf 2.0.0.
What we do: we send a json_encoded collection with pdf-data (which includes html for header, body and footer) to our Java app. There we iterate over the collection, create a byteArrayOutputStream for each pdf-data element and merge them together. We then send the results to a script which echoes it to e.g. a browser. Although the pdf is displayed correctly, we encounter errors while creating it:
com.itextpdf.io.IOException: Error at file pointer 226,416.
...
Caused by: com.itextpdf.io.IOException: xref subsection not found.
... 73 common frames omitted
If we create only one part of the collection, no error is thrown.
Iterate over collection and merge:
#RequestMapping(value = "/pdf", method = RequestMethod.POST, produces = MediaType.APPLICATION_PDF_VALUE)
public byte[] index(#RequestBody PDFDataModelCollection elements, Model model) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
PdfWriter writer = new PdfWriter(byteArrayOutputStream);
try (PdfDocument resultDoc = new PdfDocument(writer)) {
for (PDFDataModel pdfDataModel : elements.getElements()) {
PdfReader reader = new PdfReader(new ByteArrayInputStream(creationService.createDatasheet(pdfDataModel)));
try (PdfDocument sourceDoc = new PdfDocument(reader)) {
int n = sourceDoc.getNumberOfPages(); //<-- IOException on second iteration
for (int i = 1; i <= n; i++) {
PdfPage page = sourceDoc.getPage(i).copyTo(resultDoc);
resultDoc.addPage(page);
}
}
}
}
return byteArrayOutputStream.toByteArray(); //outputs the final pdf
}
Creation of part:
public byte[] createDatasheet(PDFDataModel pdfDataModel) throws IOException {
PdfWriter writer = new PdfWriter(byteArrayOutputStream);
//Initialize PDF document
PdfDocument pdfDoc = new PdfDocument(writer);
try (
Document document = new Document(pdfDoc)
) {
//header, footer, etc
//body
for (IElement element : HtmlConverter.convertToElements(pdfDataModel.getBody(), this.props)) {
document.add((IBlockElement) element);
}
footer.writeTotalNumberOnPages(pdfDoc);
}
return byteArrayOutputStream.toByteArray();
}
We are grateful for any suggestion.
In createDatasheet you appear to re-use some byteArrayOutputStream without clearing it first.
In the first iteration, therefore, everything works as desired, at the end of createDatasheet you have a single PDF file in it.
In the second iteration, though, you have two PDF files in that byteArrayOutputStream, one after the other. This concatenation does not form a valid single PDF.
Thus, byteArrayOutputStream.toByteArray() returns something broken.
To fix this, either make the byteArrayOutputStream local to createDatasheet and create a new instance every time or alternatively reset byteArrayOutputStream at the start of createDatasheet:
public byte[] createDatasheet(PDFDataModel pdfDataModel) throws IOException {
byteArrayOutputStream.reset();
PdfWriter writer = new PdfWriter(byteArrayOutputStream);
[...]

itext AcroFields form onto second page, needs to keep same template

i have a form that i have created in MS Word then converted to a PDF (Form) then i load this in using a PDF Reader, i then have a stamper created that fills in the fields, if i want to add a second page with the same template (Form) how do i do this and populate some of the fields with the same information
i have managed to get a new page with another reader but how do i stamp information onto this page as the AcroFields will have the same name.#
this is how i achieved that:
stamper.insertPage(1,PageSize.A4);
PdfReader reader = new PdfReader("/soaprintjobs/templates/STOTemplate.pdf"); //reads the original pdf
PdfImportedPage page; //writes the new pdf to file
page = stamper.getImportedPage(reader,1); //retrieve the second page of the original pdf
PdfContentByte newPageContent = stamper.getUnderContent(1); //get the over content of the first page of the new pdf
newPageContent.addTemplate(page, 0,0);
Thanks
Acroform fields have the property that fields with the same name are considered the same field. They have the same value. So if you have a field with the same name on page 1 and page 2, they will always display the same value. If you change the value on page 1, it will also change on page 2.
In some cases this is desirable. You may have a multi-page form with a reference number and want to repeat that reference number on each page. In that case you can use fields with the same name.
However, if you want to have multiple copies of the same form with different data in 1 document, you'll run into problems. You'll have to rename the form fields so they are unique.
In iText, you should not use getImportedPage() to copy Acroforms. Starting with iText 5.4.4 you can use the PdfCopy class. In earlier versions the PdfCopyFields class should be used.
Here's some sample code to copy Acroforms and rename fields. Code for iText 5.4.4 and up is in comments.
public static void main(String[] args) throws FileNotFoundException, DocumentException, IOException {
String[] inputs = { "form1.pdf", "form2.pdf" };
PdfCopyFields pcf = new PdfCopyFields(new FileOutputStream("out.pdf"));
// iText 5.4.4+
// Document document = new Document();
// PdfCopy pcf = new PdfCopy(document, new FileOutputStream("out.pdf"));
// pcf.setMergeFields();
// document.open();
int documentnumber = 0;
for (String input : inputs) {
PdfReader reader = new PdfReader(input);
documentnumber++;
// add suffix to each field name, in order to make them unique.
renameFields(reader, documentnumber);
pcf.addDocument(reader);
}
pcf.close();
// iText 5.4.4+
// document.close();
}
public static void renameFields(PdfReader reader, int documentnumber) {
Set<String> keys = new HashSet<String>(reader.getAcroFields()
.getFields().keySet());
for (String key : keys) {
reader.getAcroFields().renameField(key, key + "_" + documentnumber);
}
}

Creating a PDF back page using Itext

I am currently using iText (Java) to create a PDF document.
This PDf document is meant to be a Businesscard to be printed.
This is part of my code:
public void write() throws DocumentException, FileNotFoundException, IOException {
String extension = ".pdf";
String file = "testerPDF";
String filename = file + extension;
Document doku = new Document(PageSize.A4, 10,20,50,150);
PdfWriter writer = PdfWriter.getInstance(doku, new FileOutputStream(new File(filename)));
doku.open();
Phrase textblock = new Phrase();
// jetzt durch den Vector iterarieren
for(int i = 0; i < vector.size(); i++) {
Chunk chunk=new Chunk((String)vector.get(i));
textblock.add(chunk);
}
doku.add(new Paragraph(textblock));
doku.close();
writer.close();
}
So I need now a way to also write a Back Page for this created PDF. In such a way that the document would look like a Business card on Paper.