@@ -25,7 +25,6 @@ This file is part of the iText (R) project.
25
25
import com .itextpdf .forms .fields .PdfFormField ;
26
26
import com .itextpdf .forms .fields .AbstractPdfFormField ;
27
27
import com .itextpdf .forms .logs .FormsLogMessageConstants ;
28
- import com .itextpdf .forms .fields .PdfFormFieldMergeUtil ;
29
28
import com .itextpdf .io .logs .IoLogMessageConstant ;
30
29
import com .itextpdf .commons .utils .MessageFormatUtil ;
31
30
import com .itextpdf .kernel .pdf .IPdfPageExtraCopier ;
@@ -38,14 +37,14 @@ This file is part of the iText (R) project.
38
37
import com .itextpdf .kernel .pdf .PdfString ;
39
38
import com .itextpdf .kernel .pdf .annot .PdfAnnotation ;
40
39
40
+ import java .util .LinkedHashSet ;
41
+ import java .util .Set ;
41
42
import org .slf4j .Logger ;
42
43
import org .slf4j .LoggerFactory ;
43
44
44
45
import java .util .ArrayList ;
45
- import java .util .HashSet ;
46
46
import java .util .List ;
47
47
import java .util .Map ;
48
- import java .util .Set ;
49
48
50
49
/**
51
50
* A sample implementation of the {#link IPdfPageExtraCopier} interface which
@@ -62,6 +61,9 @@ public class PdfPageFormCopier implements IPdfPageExtraCopier {
62
61
private PdfAcroForm formTo ;
63
62
private PdfDocument documentFrom ;
64
63
private PdfDocument documentTo ;
64
+
65
+ private final Set <PdfObject > collectedFieldObjects = new LinkedHashSet <PdfObject >();
66
+
65
67
private static Logger logger = LoggerFactory .getLogger (PdfPageFormCopier .class );
66
68
67
69
@ Override
@@ -98,11 +100,30 @@ public void copy(PdfPage fromPage, PdfPage toPage) {
98
100
99
101
List <PdfAnnotation > annots = toPage .getAnnotations ();
100
102
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
+ }
104
124
}
105
- copyField (toPage , fieldsFrom , fieldsTo , annot );
125
+ } finally {
126
+ collectedFieldObjects .clear ();
106
127
}
107
128
}
108
129
@@ -115,7 +136,7 @@ private AbstractPdfFormField makeFormField(PdfObject fieldDict) {
115
136
return field ;
116
137
}
117
138
118
- private void copyField (PdfPage toPage , Map <String , PdfFormField > fieldsFrom ,
139
+ private void copyField (Map <String , PdfFormField > fieldsFrom ,
119
140
Map <String , PdfFormField > fieldsTo , PdfAnnotation currentAnnot ) {
120
141
PdfDictionary parent = currentAnnot .getPdfObject ().getAsDictionary (PdfName .Parent );
121
142
if (parent != null ) {
@@ -127,7 +148,7 @@ private void copyField(PdfPage toPage, Map<String, PdfFormField> fieldsFrom,
127
148
if (parentName == null ) {
128
149
return ;
129
150
}
130
- copyParentFormField (toPage , fieldsTo , currentAnnot , parentField );
151
+ copyParentFormField (fieldsTo , currentAnnot , parentField );
131
152
} else {
132
153
PdfString annotName = currentAnnot .getPdfObject ().getAsString (PdfName .T );
133
154
String annotNameString = null ;
@@ -142,108 +163,34 @@ private void copyField(PdfPage toPage, Map<String, PdfFormField> fieldsFrom,
142
163
if (field == null ) {
143
164
return ;
144
165
}
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 ());
147
174
}
148
- // Form may be already added to the page. PdfAcroForm will take care about it.
149
- formTo .addField (field , toPage , true );
175
+
150
176
field .updateDefaultAppearance ();
151
177
}
152
178
}
153
179
}
154
180
155
- private void copyParentFormField (PdfPage toPage , Map <String , PdfFormField > fieldsTo ,
181
+ private void copyParentFormField (Map <String , PdfFormField > fieldsTo ,
156
182
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 );
204
186
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 ));
217
190
}
218
- }
219
-
220
- String fullFieldName = fieldName .toUnicodeString ();
221
- if (null != newField .getFieldName ()) {
222
- fullFieldName = newField .getFieldName ().toUnicodeString ();
223
- }
224
191
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 ());
236
193
}
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 ;
247
194
}
248
195
249
196
private static PdfFormField getParentField (PdfDictionary parent , PdfDocument pdfDoc ) {
@@ -255,92 +202,25 @@ private static PdfFormField getParentField(PdfDictionary parent, PdfDocument pdf
255
202
return PdfFormField .makeFormField (parent , pdfDoc );
256
203
}
257
204
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 );
260
207
PdfFormField field ;
261
208
262
209
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
264
211
PdfArray kids = (PdfArray ) parent .get (PdfName .Kids );
265
212
if (kids == null ) {
266
- parent .put (PdfName .Kids , new PdfArray (fieldDic ));
213
+ parent .put (PdfName .Kids , new PdfArray (fieldDict ));
267
214
} else {
268
- kids .add (fieldDic );
215
+ if (!kids .contains (fieldDict )) {
216
+ kids .add (fieldDict );
217
+ }
269
218
}
219
+ field = createParentFieldCopy (parent , pdfDoc );
270
220
} else {
271
- field = PdfFormField .makeFormField (fieldDic , pdfDoc );
221
+ field = PdfFormField .makeFormField (fieldDict , pdfDoc );
272
222
}
273
223
274
224
return field ;
275
225
}
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
- }
346
226
}
0 commit comments