Skip to content

Commit 37b2549

Browse files
committed
Get rid of own merge fields logic in PdfPageFormCopier logic and use the one from form fields
DEVSIX-7389
1 parent 337cb59 commit 37b2549

30 files changed

+189
-223
lines changed

forms/src/main/java/com/itextpdf/forms/PdfAcroForm.java

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -209,23 +209,35 @@ public void addField(PdfFormField field) {
209209
*
210210
* @param field the {@link PdfFormField} to be added to the form
211211
* @param page the {@link PdfPage} on which to add the field
212-
* @param replaceExisted if true then the existed form field will be replaced by a new one
213-
* in case they have the same names
214212
*/
215-
public void addField(PdfFormField field, PdfPage page, boolean replaceExisted) {
213+
public void addField(PdfFormField field, PdfPage page) {
214+
addField(field, page, true);
215+
}
216+
217+
/**
218+
* This method adds the field to a specific page.
219+
*
220+
* @param field the {@link PdfFormField} to be added to the form
221+
* @param page the {@link PdfPage} on which to add the field
222+
* @param throwExceptionOnError true if the exception is expected to be thrown in case of error.
223+
*/
224+
public void addField(PdfFormField field, PdfPage page, boolean throwExceptionOnError) {
216225
if (!field.getPdfObject().containsKey(PdfName.T)) {
217-
throw new PdfException(FormsExceptionMessageConstant.FORM_FIELD_MUST_HAVE_A_NAME);
226+
if (throwExceptionOnError) {
227+
throw new PdfException(FormsExceptionMessageConstant.FORM_FIELD_MUST_HAVE_A_NAME);
228+
} else {
229+
LOGGER.warn(FormsLogMessageConstants.FORM_FIELD_MUST_HAVE_A_NAME);
230+
return;
231+
}
218232
}
219233

220-
if (!replaceExisted) {
221-
PdfFormFieldMergeUtil.mergeKidsWithSameNames(field, true);
222-
}
234+
PdfFormFieldMergeUtil.mergeKidsWithSameNames(field, throwExceptionOnError);
223235

224236
PdfDictionary fieldDict = field.getPdfObject();
225237
// PdfPageFormCopier expects that we replace existed field by a new one in case they have the same names.
226238
String fieldName = field.getFieldName().toUnicodeString();
227-
if (replaceExisted || !fields.containsKey(fieldName) ||
228-
!PdfFormFieldMergeUtil.mergeTwoFieldsWithTheSameNames(fields.get(fieldName), field, true)) {
239+
if (!fields.containsKey(fieldName) ||
240+
!PdfFormFieldMergeUtil.mergeTwoFieldsWithTheSameNames(fields.get(fieldName), field, throwExceptionOnError)) {
229241
PdfArray fieldsArray = getFields();
230242
fieldsArray.add(fieldDict);
231243
fieldsArray.setModified();
@@ -240,16 +252,6 @@ public void addField(PdfFormField field, PdfPage page, boolean replaceExisted) {
240252
setModified();
241253
}
242254

243-
/**
244-
* This method adds the field to a specific page.
245-
*
246-
* @param field the {@link PdfFormField} to be added to the form
247-
* @param page the {@link PdfPage} on which to add the field
248-
*/
249-
public void addField(PdfFormField field, PdfPage page) {
250-
addField(field, page, false);
251-
}
252-
253255
/**
254256
* This method merges field with its annotation and places it on the given
255257
* page. This method also work if the field has more than one widget

forms/src/main/java/com/itextpdf/forms/PdfPageFormCopier.java

Lines changed: 56 additions & 176 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ This file is part of the iText (R) project.
2525
import com.itextpdf.forms.fields.PdfFormField;
2626
import com.itextpdf.forms.fields.AbstractPdfFormField;
2727
import com.itextpdf.forms.logs.FormsLogMessageConstants;
28-
import com.itextpdf.forms.fields.PdfFormFieldMergeUtil;
2928
import com.itextpdf.io.logs.IoLogMessageConstant;
3029
import com.itextpdf.commons.utils.MessageFormatUtil;
3130
import com.itextpdf.kernel.pdf.IPdfPageExtraCopier;
@@ -38,14 +37,14 @@ This file is part of the iText (R) project.
3837
import com.itextpdf.kernel.pdf.PdfString;
3938
import com.itextpdf.kernel.pdf.annot.PdfAnnotation;
4039

40+
import java.util.LinkedHashSet;
41+
import java.util.Set;
4142
import org.slf4j.Logger;
4243
import org.slf4j.LoggerFactory;
4344

4445
import java.util.ArrayList;
45-
import java.util.HashSet;
4646
import java.util.List;
4747
import java.util.Map;
48-
import java.util.Set;
4948

5049
/**
5150
* A sample implementation of the {#link IPdfPageExtraCopier} interface which
@@ -62,6 +61,9 @@ public class PdfPageFormCopier implements IPdfPageExtraCopier {
6261
private PdfAcroForm formTo;
6362
private PdfDocument documentFrom;
6463
private PdfDocument documentTo;
64+
65+
private final Set<PdfObject> collectedFieldObjects = new LinkedHashSet<PdfObject>();
66+
6567
private static Logger logger = LoggerFactory.getLogger(PdfPageFormCopier.class);
6668

6769
@Override
@@ -98,11 +100,30 @@ public void copy(PdfPage fromPage, PdfPage toPage) {
98100

99101
List<PdfAnnotation> annots = toPage.getAnnotations();
100102

101-
for (PdfAnnotation annot : annots) {
102-
if (!annot.getSubtype().equals(PdfName.Widget)) {
103-
continue;
103+
try {
104+
for (PdfAnnotation annot : annots) {
105+
if (!annot.getSubtype().equals(PdfName.Widget)) {
106+
continue;
107+
}
108+
copyField(fieldsFrom, fieldsTo, annot);
109+
}
110+
for (PdfObject fieldObject : collectedFieldObjects) {
111+
PdfFormField field = PdfFormField.makeFormField(fieldObject, documentTo);
112+
String fieldName = field.getFieldName().toUnicodeString();
113+
if (field.equals(fieldsTo.get(fieldName))) {
114+
// Here the 'field' might wrap the same pdfObject as fieldsTo.get(fieldName).
115+
// But fieldsTo.get(fieldName) might have less childFields attached
116+
// (and the same amount of Kids in pdf object it wraps, see createParentFieldCopy
117+
// where we work with the Kids array directly). Our merge logic doesn't work
118+
// with such not synchronised fields. So that we replace it with newly created field
119+
// which contains all childFields.
120+
formTo.replaceField(fieldName, field);
121+
} else {
122+
formTo.addField(field, toPage, false);
123+
}
104124
}
105-
copyField(toPage, fieldsFrom, fieldsTo, annot);
125+
} finally {
126+
collectedFieldObjects.clear();
106127
}
107128
}
108129

@@ -115,7 +136,7 @@ private AbstractPdfFormField makeFormField(PdfObject fieldDict) {
115136
return field;
116137
}
117138

118-
private void copyField(PdfPage toPage, Map<String, PdfFormField> fieldsFrom,
139+
private void copyField(Map<String, PdfFormField> fieldsFrom,
119140
Map<String, PdfFormField> fieldsTo, PdfAnnotation currentAnnot) {
120141
PdfDictionary parent = currentAnnot.getPdfObject().getAsDictionary(PdfName.Parent);
121142
if (parent != null) {
@@ -127,7 +148,7 @@ private void copyField(PdfPage toPage, Map<String, PdfFormField> fieldsFrom,
127148
if (parentName == null) {
128149
return;
129150
}
130-
copyParentFormField(toPage, fieldsTo, currentAnnot, parentField);
151+
copyParentFormField(fieldsTo, currentAnnot, parentField);
131152
} else {
132153
PdfString annotName = currentAnnot.getPdfObject().getAsString(PdfName.T);
133154
String annotNameString = null;
@@ -142,108 +163,34 @@ private void copyField(PdfPage toPage, Map<String, PdfFormField> fieldsFrom,
142163
if (field == null) {
143164
return;
144165
}
145-
if (fieldsTo.get(annotNameString) != null) {
146-
field = mergeFieldsWithTheSameName(field);
166+
167+
if (!collectedFieldObjects.contains(field.getPdfObject())) {
168+
if (fieldsTo.get(annotNameString) != null) {
169+
logger.warn(MessageFormatUtil.format(IoLogMessageConstant.DOCUMENT_ALREADY_HAS_FIELD,
170+
annotNameString));
171+
}
172+
173+
collectedFieldObjects.add(field.getPdfObject());
147174
}
148-
// Form may be already added to the page. PdfAcroForm will take care about it.
149-
formTo.addField(field, toPage, true);
175+
150176
field.updateDefaultAppearance();
151177
}
152178
}
153179
}
154180

155-
private void copyParentFormField(PdfPage toPage, Map<String, PdfFormField> fieldsTo,
181+
private void copyParentFormField(Map<String, PdfFormField> fieldsTo,
156182
PdfAnnotation annot, PdfFormField parentField) {
157-
PdfString parentName = parentField.getFieldName();
158-
// parentField should be the root field
159-
if (!fieldsTo.containsKey(parentName.toUnicodeString())) {
160-
// no such field, hence we should simply add it
161-
PdfFormField field = createParentFieldCopy(annot.getPdfObject(), documentTo);
162-
PdfArray kids = field.getKids();
163-
field.getPdfObject().remove(PdfName.Kids);
164-
formTo.addField(field, toPage, true);
165-
field.getPdfObject().put(PdfName.Kids, kids);
166-
} else {
167-
// annot is either a field (field name will not be null) or a widget (field name is null)
168-
AbstractPdfFormField field = makeFormField(annot.getPdfObject());
169-
if (field == null) {
170-
return;
171-
}
172-
PdfString fieldName = field.getFieldName();
173-
if (fieldName != null) {
174-
PdfFormField existingField = fieldsTo.get(fieldName.toUnicodeString());
175-
if (existingField != null) {
176-
PdfFormField mergedField = mergeFieldsWithTheSameName(field);
177-
formTo.getDirectFormFields().put(mergedField.getFieldName().toUnicodeString(), mergedField);
178-
} else {
179-
HashSet<String> existingFields = new HashSet<>();
180-
getAllFieldNames(formTo.getFields(), existingFields);
181-
addChildToExistingParent(annot.getPdfObject(), existingFields,
182-
fieldsTo);
183-
}
184-
} else {
185-
if (!parentField.getKids().contains(field.getPdfObject())
186-
&& formTo.getFields().contains(parentField.getPdfObject())) {
187-
// annot's parent is already a field of the resultant document,
188-
// hence we only need to update its children
189-
HashSet<String> existingFields = new HashSet<>();
190-
getAllFieldNames(formTo.getFields(), existingFields);
191-
addChildToExistingParent(annot.getPdfObject(), existingFields);
192-
} else {
193-
// its parent is not a field of the resultant document, but the latter contains
194-
// a field of the same name, therefore we should merge them (note that merging in this context
195-
// differs from merging a widget and an annotation into a single entity)
196-
PdfFormField mergedField = mergeFieldsWithTheSameName(field);
197-
// we need to add the field not to its representation (#getFormFields()), but to
198-
// /Fields entry of the acro form
199-
formTo.addField(mergedField, toPage, true);
200-
}
201-
}
202-
}
203-
}
183+
String parentName = parentField.getFieldName().toUnicodeString();
184+
PdfFormField existingField = fieldsTo.get(parentName);
185+
PdfFormField field = createParentFieldCopy(annot.getPdfObject(), documentTo);
204186

205-
private PdfFormField mergeFieldsWithTheSameName(AbstractPdfFormField newField) {
206-
PdfString fieldName = newField.getPdfObject().getAsString(PdfName.T);
207-
208-
PdfDictionary parent = newField.getParent();
209-
if (parent != null) {
210-
newField.setParent(PdfFormField.makeFormField(parent, newField.getDocument()));
211-
if (fieldName == null) {
212-
if (newField.isTerminalFormField()) {
213-
fieldName = new PdfString(parent.getAsString(PdfName.T).toUnicodeString() + ".");
214-
} else {
215-
fieldName = parent.getAsString(PdfName.T);
216-
}
187+
if (!collectedFieldObjects.contains(field.getPdfObject())) {
188+
if (existingField != null) {
189+
logger.warn(MessageFormatUtil.format(IoLogMessageConstant.DOCUMENT_ALREADY_HAS_FIELD, parentName));
217190
}
218-
}
219-
220-
String fullFieldName = fieldName.toUnicodeString();
221-
if (null != newField.getFieldName()) {
222-
fullFieldName = newField.getFieldName().toUnicodeString();
223-
}
224191

225-
logger.warn(MessageFormatUtil.format(IoLogMessageConstant.DOCUMENT_ALREADY_HAS_FIELD, fullFieldName));
226-
227-
PdfFormField existingField = formTo.getField(fullFieldName);
228-
if (existingField.isFlushed() && newField instanceof PdfFormField) {
229-
int index = 0;
230-
do {
231-
index++;
232-
((PdfFormField)newField).setFieldName(fieldName.toUnicodeString() + "_#" + index);
233-
fullFieldName = newField.getFieldName().toUnicodeString();
234-
} while (formTo.getField(fullFieldName) != null);
235-
return (PdfFormField)newField;
192+
collectedFieldObjects.add(field.getPdfObject());
236193
}
237-
238-
formTo.getFields().remove(existingField.getPdfObject());
239-
240-
if (newField instanceof PdfFormField) {
241-
PdfFormFieldMergeUtil.mergeTwoFieldsWithTheSameNames(existingField, (PdfFormField) newField, true);
242-
} else {
243-
existingField.addKid(newField);
244-
}
245-
246-
return existingField;
247194
}
248195

249196
private static PdfFormField getParentField(PdfDictionary parent, PdfDocument pdfDoc) {
@@ -255,92 +202,25 @@ private static PdfFormField getParentField(PdfDictionary parent, PdfDocument pdf
255202
return PdfFormField.makeFormField(parent, pdfDoc);
256203
}
257204

258-
private PdfFormField createParentFieldCopy(PdfDictionary fieldDic, PdfDocument pdfDoc) {
259-
PdfDictionary parent = fieldDic.getAsDictionary(PdfName.Parent);
205+
private PdfFormField createParentFieldCopy(PdfDictionary fieldDict, PdfDocument pdfDoc) {
206+
PdfDictionary parent = fieldDict.getAsDictionary(PdfName.Parent);
260207
PdfFormField field;
261208

262209
if (parent != null) {
263-
field = createParentFieldCopy(parent, pdfDoc);
210+
// Here we operate with Kids array to do not run split/merge logic before PdfAcroForm.addField
264211
PdfArray kids = (PdfArray) parent.get(PdfName.Kids);
265212
if (kids == null) {
266-
parent.put(PdfName.Kids, new PdfArray(fieldDic));
213+
parent.put(PdfName.Kids, new PdfArray(fieldDict));
267214
} else {
268-
kids.add(fieldDic);
215+
if (!kids.contains(fieldDict)) {
216+
kids.add(fieldDict);
217+
}
269218
}
219+
field = createParentFieldCopy(parent, pdfDoc);
270220
} else {
271-
field = PdfFormField.makeFormField(fieldDic, pdfDoc);
221+
field = PdfFormField.makeFormField(fieldDict, pdfDoc);
272222
}
273223

274224
return field;
275225
}
276-
277-
private void addChildToExistingParent(PdfDictionary fieldDic, Set<String> existingFields) {
278-
PdfDictionary parent = fieldDic.getAsDictionary(PdfName.Parent);
279-
if (parent == null) {
280-
return;
281-
}
282-
283-
PdfString parentName = parent.getAsString(PdfName.T);
284-
if (parentName != null) {
285-
String name = parentName.toUnicodeString();
286-
if (existingFields.contains(name)) {
287-
PdfArray kids = parent.getAsArray(PdfName.Kids);
288-
kids.add(fieldDic);
289-
} else {
290-
parent.put(PdfName.Kids, new PdfArray(fieldDic));
291-
addChildToExistingParent(parent, existingFields);
292-
}
293-
}
294-
}
295-
296-
private void addChildToExistingParent(PdfDictionary fieldDic, Set<String> existingFields,
297-
Map<String, PdfFormField> fieldsTo) {
298-
PdfDictionary parent = fieldDic.getAsDictionary(PdfName.Parent);
299-
if (parent == null) {
300-
return;
301-
}
302-
303-
PdfString parentName = parent.getAsString(PdfName.T);
304-
if (parentName != null) {
305-
String name = parentName.toUnicodeString();
306-
if (existingFields.contains(name)) {
307-
PdfArray kids = parent.getAsArray(PdfName.Kids);
308-
for (PdfObject kid : kids) {
309-
if (((PdfDictionary) kid).get(PdfName.T) != null &&
310-
((PdfDictionary) kid).get(PdfName.T).equals(fieldDic.get(PdfName.T))) {
311-
AbstractPdfFormField kidField = makeFormField(kid);
312-
AbstractPdfFormField field = makeFormField(fieldDic);
313-
if (kidField == null || field == null) {
314-
continue;
315-
}
316-
fieldsTo.put(kidField.getFieldName().toUnicodeString(), (PdfFormField)kidField);
317-
PdfFormField mergedField = mergeFieldsWithTheSameName(field);
318-
formTo.getDirectFormFields().put(mergedField.getFieldName().toUnicodeString(), mergedField);
319-
return;
320-
}
321-
}
322-
kids.add(fieldDic);
323-
} else {
324-
parent.put(PdfName.Kids, new PdfArray(fieldDic));
325-
addChildToExistingParent(parent, existingFields);
326-
}
327-
}
328-
}
329-
330-
private void getAllFieldNames(PdfArray fields, Set<String> existingFields) {
331-
for (PdfObject field : fields) {
332-
if (field.isFlushed()) {
333-
continue;
334-
}
335-
PdfDictionary dic = (PdfDictionary) field;
336-
PdfString name = dic.getAsString(PdfName.T);
337-
if (name != null) {
338-
existingFields.add(name.toUnicodeString());
339-
}
340-
PdfArray kids = dic.getAsArray(PdfName.Kids);
341-
if (kids != null) {
342-
getAllFieldNames(kids, existingFields);
343-
}
344-
}
345-
}
346226
}

0 commit comments

Comments
 (0)