@@ -25,7 +25,6 @@ This file is part of the iText (R) project.
2525import com .itextpdf .forms .fields .PdfFormField ;
2626import com .itextpdf .forms .fields .AbstractPdfFormField ;
2727import com .itextpdf .forms .logs .FormsLogMessageConstants ;
28- import com .itextpdf .forms .fields .PdfFormFieldMergeUtil ;
2928import com .itextpdf .io .logs .IoLogMessageConstant ;
3029import com .itextpdf .commons .utils .MessageFormatUtil ;
3130import com .itextpdf .kernel .pdf .IPdfPageExtraCopier ;
@@ -38,14 +37,14 @@ This file is part of the iText (R) project.
3837import com .itextpdf .kernel .pdf .PdfString ;
3938import com .itextpdf .kernel .pdf .annot .PdfAnnotation ;
4039
40+ import java .util .LinkedHashSet ;
41+ import java .util .Set ;
4142import org .slf4j .Logger ;
4243import org .slf4j .LoggerFactory ;
4344
4445import java .util .ArrayList ;
45- import java .util .HashSet ;
4646import java .util .List ;
4747import 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