How to merge forms from different files into one PDF?

Tags: merge documentsformsmerge fieldsiText 7

I currently have a PdfReader and a PdfStamper that I am using to fill out a form. I now have to add another PDF to the end of the form I have been filling out, but when I do this, I lose all the interactive fields. This is what I currently have:

public static void addSectionThirteenPdf(
    PdfStamper stamper, Rectangle pageSize, int pageIndex){
    PdfReader reader = new PdfReader("section13.pdf");
    AcroFields fields = reader.getAcroFields();
    fields.renameField("field", "field" + pageIndex);
    stamper.insertPage(pageIndex, pageSize);
    stamper.replacePage(reader, 1, pageIndex);
}
The way that I am creating the original document is like this.

PdfReader pdfTemplate = new PdfReader(pathToPdf);
PdfStamper stamper = new PdfStamper(pdfTemplate, output);
stamper.setFormFlattening(true);
AcroFields fields = stamper.getAcroFields();
fields.setField(key, value);
stamper.close();
Is there a way to merge using the first piece of code and merge both of the forms together?

Posted on StackOverflow on Feb 17, 2015 by user3585563

Depending on what you want exactly, different scenarios are possible, but in any case: you are doing it wrong. You should use copyPagesTo() method in iText 7 to merge documents.

The different scenarios are explained in the following video tutorial.

Merging different forms (having different fields)

If you want to merge different forms without flattening them, you should use copyPagesTo() as is done in the MergeForms example:

protected void manipulatePdf(String dest, PdfReader[] readers) throws FileNotFoundException {
    PdfDocument pdfDoc = new PdfDocument(new PdfWriter(dest));
    pdfDoc.initializeOutlines();
    for (PdfReader reader : readers) {
        PdfDocument readerDoc = new PdfDocument(reader);
        readerDoc.copyPagesTo(1, readerDoc.getNumberOfPages(), pdfDoc, new PdfPageFormCopier());
        readerDoc.close();
    }
    pdfDoc.close();
}

In this case, readers is an array of PdfReader instances containing different forms (with different field names), hence we use copyPagesTo() and we make sure that we don't forget to use the new PdfPageFormCopier() as the last parameter, or the fields won't be copied.

Merging identical forms (having identical fields)

In this case, we need to rename the fields, because we probably want different values on different pages. In PDF a field can only have a single value. If you merge identical forms, you have multiple visualizations of the same field, but each visualization will show the same value (because in reality, there is only one field).

Let's take a look at the MergeForms2 example:

protected void manipulatePdf(String dest) throws Exception {
    PdfDocument pdfDoc = new PdfDocument(new PdfWriter(dest));
    pdfDoc.initializeOutlines();
    for (int i = 0; i < 3; ) {
        PdfDocument readerDoc = new PdfDocument(new PdfReader(
                new RandomAccessSourceFactory().createSource(renameFields(src, ++i)),
                new ReaderProperties()));
        readerDoc.copyPagesTo(1, readerDoc.getNumberOfPages(), pdfDoc, new PdfPageFormCopier());
        readerDoc.close();
    }
    pdfDoc.close();
}
 
protected byte[] renameFields(String src, int i) throws IOException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    PdfDocument pdfDoc = new PdfDocument(new PdfReader(src), new PdfWriter(baos));
 
    PdfAcroForm form = PdfAcroForm.getAcroForm(pdfDoc, true);
    for (PdfFormField field : form.getFormFields().values()) {
        field.setFieldName(String.format("%s_%d", field.getFieldName().toString(), i));
    }
 
    pdfDoc.close();
    return baos.toByteArray();
}

As you can see, the renameFields() method creates a new document in memory and rename the field names using setFieldName() method. Notice that we use RandomAccessSourceFactory() to put the source pdf with renamed AcroForm fields to the PdfReader constructor.

Merging flattened forms

The FillFlattenMerge2 shows how to merge identical forms that are filled out and flattened correctly (in iText 7 you should use the smartMode property in PdfWriter):

protected void manipulatePdf(String dest) throws Exception {
    PdfWriter writer = new PdfWriter(dest);
    writer.setSmartMode(true);
    PdfDocument pdfDoc = new PdfDocument(writer);
    pdfDoc.initializeOutlines();
 
    ByteArrayOutputStream baos;
    PdfReader reader;
    PdfDocument pdfInnerDoc;
    Map<String, PdfFormField> fields;
    PdfAcroForm form;
    StringTokenizer tokenizer;
 
    BufferedReader br = new BufferedReader(new FileReader(data));
    String line = br.readLine();
    while ((line = br.readLine()) != null) {
        // create a PDF in memory
        baos = new ByteArrayOutputStream();
        reader = new PdfReader(src);
        pdfInnerDoc = new PdfDocument(reader, new PdfWriter(baos));
        form = PdfAcroForm.getAcroForm(pdfInnerDoc, true);
        tokenizer = new StringTokenizer(line, ";");
        fields = form.getFormFields();
        fields.get("name").setValue(tokenizer.nextToken());
        fields.get("abbr").setValue(tokenizer.nextToken());
        fields.get("capital").setValue(tokenizer.nextToken());
        fields.get("city").setValue(tokenizer.nextToken());
        fields.get("population").setValue(tokenizer.nextToken());
        fields.get("surface").setValue(tokenizer.nextToken());
        fields.get("timezone1").setValue(tokenizer.nextToken());
        fields.get("timezone2").setValue(tokenizer.nextToken());
        fields.get("dst").setValue(tokenizer.nextToken());
        form.flattenFields();
        pdfInnerDoc.close();
 
        pdfInnerDoc = new PdfDocument(new PdfReader(new ByteArrayInputStream(baos.toByteArray())));
        pdfInnerDoc.copyPagesTo(1, pdfInnerDoc.getNumberOfPages(), pdfDoc, new PdfPageFormCopier());
        pdfInnerDoc.close();
    }
    br.close();
 
    pdfDoc.close();
}

Now it's up to you to decide which scenario fits your specific requirement.

Click this link if you want to see how to answer this question in iText 5.