I have made the following ticket in the issue tracker at iText Group:
A problem is caused by the fact that iText reads the field items into
a HashMap
, hence there is no way to predict in which order they will
be flattened. This usually isn't a problem. I don't think this problem
occurs in case you don't flatten the PDF, because in that case, the
appearance is stored in the widget annotations and it's up to the PDF
viewer to decide which field covers another one in case of overlapping
fields.
However, if form fields overlap, then you can't predict which field
will cover which when flattening.
Suppose that we'd use a TreeMap
instead of a HashMap
, would this
solve the problem? Not really, because which Comparator
would we
use? Sometimes a Tab-order is defined, but not always. If it's not
defined, should we order the fields in the order in which they appear
in the /Fields
array? Or does it make more sense to order them based
on the order of the widget annotations in the /Annots
array? Another
option is to order them based on their position on the page. In short:
this is not a decision iText should make.
However, if somebody would like to solve this problem, we could create
a Comparator
member variable for PdfStamperImp
. If such a
Comparator
is provided (we could even provide some implementations),
then the flattening process would be executed in the order defined by
the Comparator
.
This ticket has received a very low priority (I assume that you're not a customer of one of the iText Software companies), but while writing this ticket, I had another idea.
I already referred to underline portion of text using iTextSharp in the comments. In this case, you'd get all the field positions (using the getFieldPositions()
method) and draw all the contents in the right order using ColumnText
. This approach has several disadvantages: in order for the font, font size, font color to be correct, you'd have to examine the fields. That requires some programming.
I am now posting this as an answer, because I have a much better alternative: fill out the form in two passes! This is shown in the FillFormFieldOrder example. We fill out the form src
resulting in the flattened form dest
like this:
public void manipulatePdf(String src, String dest) throws DocumentException, IOException {
go2(go1(src), dest);
}
As you can see, we execute the go1()
method first:
public byte[] go1(String src) throws IOException, DocumentException {
PdfReader reader = new PdfReader(src);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PdfStamper stamper = new PdfStamper(reader, baos);
AcroFields form = stamper.getAcroFields();
form.setField("sunday_1", "1");
form.setField("sunday_2", "2");
form.setField("sunday_3", "3");
form.setField("sunday_4", "4");
form.setField("sunday_5", "5");
form.setField("sunday_6", "6");
stamper.setFormFlattening(true);
stamper.partialFormFlattening("sunday_1");
stamper.partialFormFlattening("sunday_2");
stamper.partialFormFlattening("sunday_3");
stamper.partialFormFlattening("sunday_4");
stamper.partialFormFlattening("sunday_5");
stamper.partialFormFlattening("sunday_6");
stamper.close();
reader.close();
return baos.toByteArray();
}
This fills out all the sunday_x
fields and uses partial form flattening to flatten only those fields. The go1()
method takes src
as parameter and returns a byte[]
will the partially flattened form.
The byte[]
will be used as a parameter for the go2()
method, that takes dest
as its second parameter. Now we are going to fill out the sunday_x_notes
fields:
public void go2(byte[] src, String dest) throws IOException, DocumentException {
PdfReader reader = new PdfReader(src);
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(dest));
AcroFields form = stamper.getAcroFields();
form.setField("sunday_1_notes", "It's Sunday today, let's go to the sea");
form.setField("sunday_2_notes", "It's Sunday today, let's go to the park");
form.setField("sunday_3_notes", "It's Sunday today, let's go to the beach");
form.setField("sunday_4_notes", "It's Sunday today, let's go to the woods");
form.setField("sunday_5_notes", "It's Sunday today, let's go to the lake");
form.setField("sunday_6_notes", "It's Sunday today, let's go to the river");
stamper.setFormFlattening(true);
stamper.close();
reader.close();
}
As you can see, we now flatten all the fields. The result looks like this:

Now, you no longer have to worry about the order of the fields, not in the /Fields
array, not in the /Annots
array. The fields are filled out in the exact order you want to. The notes cover the dates now, instead of the other way round.