You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pdfbox.apache.org by ms...@apache.org on 2020/11/01 07:24:52 UTC

svn commit: r1883031 - in /pdfbox/branches/2.0/pdfbox/src: main/java/org/apache/pdfbox/pdmodel/fixup/ main/java/org/apache/pdfbox/pdmodel/fixup/processor/ test/java/org/apache/pdfbox/pdmodel/interactive/form/

Author: msahyoun
Date: Sun Nov  1 07:24:52 2020
New Revision: 1883031

URL: http://svn.apache.org/viewvc?rev=1883031&view=rev
Log:
PDFBOX-3891: handle adding nested fields; move handling decision from processor to fixup

Modified:
    pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/fixup/AcroFormDefaultFixup.java
    pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/fixup/processor/AcroFormOrphanWidgetsProcessor.java
    pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/interactive/form/PDAcroFormFromAnnotsTest.java

Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/fixup/AcroFormDefaultFixup.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/fixup/AcroFormDefaultFixup.java?rev=1883031&r1=1883030&r2=1883031&view=diff
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/fixup/AcroFormDefaultFixup.java (original)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/fixup/AcroFormDefaultFixup.java Sun Nov  1 07:24:52 2020
@@ -20,6 +20,7 @@ import org.apache.pdfbox.pdmodel.PDDocum
 import org.apache.pdfbox.pdmodel.fixup.processor.AcroFormDefaultsProcessor;
 import org.apache.pdfbox.pdmodel.fixup.processor.AcroFormGenerateAppearancesProcessor;
 import org.apache.pdfbox.pdmodel.fixup.processor.AcroFormOrphanWidgetsProcessor;
+import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
 
 public class AcroFormDefaultFixup extends AbstractFixup
 { 
@@ -30,8 +31,29 @@ public class AcroFormDefaultFixup extend
 
     @Override
     public void apply() {
-        new AcroFormOrphanWidgetsProcessor(document).process();
         new AcroFormDefaultsProcessor(document).process();
-        new AcroFormGenerateAppearancesProcessor(document).process();
+
+        /*
+         * Get the AcroForm in it's current state.
+         *
+         * Also note: getAcroForm() applies a default fixup which this processor
+         * is part of. So keep the null parameter otherwise this will end
+         * in an endless recursive call
+         */
+        PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm(null);
+
+        // PDFBOX-4985
+        // build the visual appearance as there is none for the widgets
+        if (acroForm != null && acroForm.getNeedAppearances())
+        {
+            if (acroForm.getFields().isEmpty())
+            {
+                new AcroFormOrphanWidgetsProcessor(document).process();
+            }
+            
+            // PDFBOX-4985
+            // build the visual appearance as there is none for the widgets
+            new AcroFormGenerateAppearancesProcessor(document).process();
+        }    
     }
 } 
\ No newline at end of file

Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/fixup/processor/AcroFormOrphanWidgetsProcessor.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/fixup/processor/AcroFormOrphanWidgetsProcessor.java?rev=1883031&r1=1883030&r2=1883031&view=diff
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/fixup/processor/AcroFormOrphanWidgetsProcessor.java (original)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/fixup/processor/AcroFormOrphanWidgetsProcessor.java Sun Nov  1 07:24:52 2020
@@ -17,11 +17,14 @@
 package org.apache.pdfbox.pdmodel.fixup.processor;
 
 import java.io.IOException;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.fontbox.ttf.TrueTypeFont;
+import org.apache.pdfbox.cos.COSDictionary;
 import org.apache.pdfbox.cos.COSName;
 import org.apache.pdfbox.pdmodel.PDDocument;
 import org.apache.pdfbox.pdmodel.PDPage;
@@ -63,9 +66,7 @@ public class AcroFormOrphanWidgetsProces
          */
         PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm(null);
 
-        // PDFBOX-4985 AcroForm with NeedAppearances true and empty fields array
-        // but Widgets in page annotations
-        if (acroForm != null && acroForm.getNeedAppearances() && acroForm.getFields().isEmpty())
+        if (acroForm != null)
         {            
             resolveFieldsFromWidgets(acroForm);
         } 
@@ -73,32 +74,78 @@ public class AcroFormOrphanWidgetsProces
 
     private void resolveFieldsFromWidgets(PDAcroForm acroForm)
     {
+        Map<String, PDField> nonTerminalFieldsMap = new HashMap<String, PDField>();
+
         LOG.debug("rebuilding fields from widgets");
         List<PDField> fields = acroForm.getFields();
         for (PDPage page : document.getPages())
         {
             try
             {
-                List<PDAnnotation> annots = page.getAnnotations();
-                for (PDAnnotation annot : annots)
-                {
-                    if (annot instanceof PDAnnotationWidget)
-                    {
-                        PDField field = PDFieldFactory.createField(acroForm, annot.getCOSObject(), null);
-                        if (field instanceof PDVariableText)
-                        {
-                            ensureFontResources(acroForm.getDefaultResources(), (PDVariableText) field);
-                        }
-                        fields.add(field);
-                    }
-                }
+                handleAnnotations(acroForm, fields, page.getAnnotations(), nonTerminalFieldsMap);
             }
             catch (IOException ioe)
             {
                 LOG.debug("couldn't read annotations for page " + ioe.getMessage());
             }
         }
+
         acroForm.setFields(fields);
+
+        // ensure that PDVariableText fields have the neccesary resources
+        for (PDField field : acroForm.getFieldTree())
+        {
+            if (field instanceof PDVariableText)
+            {
+                ensureFontResources(acroForm.getDefaultResources(), (PDVariableText) field);
+            }
+        }        
+    }
+
+    private void handleAnnotations(PDAcroForm acroForm, List<PDField> fields, List<PDAnnotation> annotations, Map<String, PDField> nonTerminalFieldsMap)
+    {
+        for (PDAnnotation annot : annotations)
+        {
+            if (annot instanceof PDAnnotationWidget)
+            {
+                if (annot.getCOSObject().containsKey(COSName.PARENT))
+                {
+                    PDField resolvedField = resolveNonRootField(acroForm, (PDAnnotationWidget) annot, nonTerminalFieldsMap);
+                    if (resolvedField != null)
+                    {
+                        fields.add(resolvedField);
+                    }
+                }
+                else
+                {
+                    fields.add(PDFieldFactory.createField(acroForm, annot.getCOSObject(), null));
+                }
+            }
+        }
+    }
+
+    /*
+     *  Widgets having a /Parent entry are non root fields. Go up until the root node is found
+     *  and handle from there.
+     */
+    private PDField resolveNonRootField(PDAcroForm acroForm, PDAnnotationWidget widget, Map<String, PDField> nonTerminalFieldsMap)
+    {
+        COSDictionary parent = widget.getCOSObject().getCOSDictionary(COSName.PARENT);
+        while (parent.containsKey(COSName.PARENT))
+        {
+            parent = parent.getCOSDictionary(COSName.PARENT);
+        }
+        
+        if (nonTerminalFieldsMap.get(parent.getString(COSName.T)) == null)
+        {
+            PDField field = PDFieldFactory.createField(acroForm, parent, null);
+            nonTerminalFieldsMap.put(field.getFullyQualifiedName(),field);
+
+            return field;
+        }
+
+        // this should not happen
+        return null;
     }
 
     /*

Modified: pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/interactive/form/PDAcroFormFromAnnotsTest.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/interactive/form/PDAcroFormFromAnnotsTest.java?rev=1883031&r1=1883030&r2=1883031&view=diff
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/interactive/form/PDAcroFormFromAnnotsTest.java (original)
+++ pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/interactive/form/PDAcroFormFromAnnotsTest.java Sun Nov  1 07:24:52 2020
@@ -17,9 +17,12 @@
 package org.apache.pdfbox.pdmodel.interactive.form;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 
 import java.io.IOException;
 import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
 
 import org.apache.pdfbox.cos.COSArray;
 import org.apache.pdfbox.cos.COSDictionary;
@@ -27,7 +30,9 @@ import org.apache.pdfbox.cos.COSName;
 import org.apache.pdfbox.io.IOUtils;
 import org.apache.pdfbox.pdmodel.PDDocument;
 import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
+import org.apache.pdfbox.pdmodel.fixup.AbstractFixup;
 import org.apache.pdfbox.pdmodel.fixup.AcroFormDefaultFixup;
+import org.apache.pdfbox.pdmodel.fixup.processor.AcroFormOrphanWidgetsProcessor;
 import org.junit.Test;
 
 /**
@@ -161,4 +166,117 @@ public class PDAcroFormFromAnnotsTest
             IOUtils.closeQuietly(testPdf);
         }
     }
+
+    /**
+     * PDFBOX-3891 AcroForm with empty fields entry
+     * 
+     * With the default correction nothing shall be added
+     * 
+     * @throws IOException
+     */
+    @Test
+    public void testFromAnnots3891DontCreateFields() throws IOException
+    {
+
+        String sourceUrl = "https://issues.apache.org/jira/secure/attachment/12881055/merge-test.pdf";
+
+        PDDocument testPdf = null;
+        try
+        {
+            testPdf = PDDocument.load(new URL(sourceUrl).openStream());
+            PDDocumentCatalog catalog = testPdf.getDocumentCatalog();
+            // need to do a low level cos access as the PDModel access will build the AcroForm
+            COSDictionary cosAcroForm = (COSDictionary) catalog.getCOSObject().getDictionaryObject(COSName.ACRO_FORM);
+            COSArray cosFields = (COSArray) cosAcroForm.getDictionaryObject(COSName.FIELDS);
+            assertEquals("Initially there shall be 0 fields", 0, cosFields.size());
+            PDAcroForm acroForm = catalog.getAcroForm();
+            assertEquals("After call with default correction there shall be 0 fields", 0, acroForm.getFields().size());
+        }
+        finally
+        {
+            IOUtils.closeQuietly(testPdf);
+        }
+    }
+
+    /**
+     * PDFBOX-3891 AcroForm with empty fields entry
+     * 
+     * Special fixup to create fields
+     * 
+     * @throws IOException
+     */
+    @Test
+    public void testFromAnnots3891CreateFields() throws IOException
+    {
+
+        String sourceUrl = "https://issues.apache.org/jira/secure/attachment/12881055/merge-test.pdf";
+        String acrobatSourceUrl = "https://issues.apache.org/jira/secure/attachment/13014447/merge-test-na-acrobat.pdf";
+
+        int numFormFieldsByAcrobat = 0;
+
+        // will build the expected fields using the acrobat source document
+        Map<String, PDField> fieldsByName = new HashMap<String, PDField>();
+
+        PDDocument testPdf = null;
+        try
+        {
+            testPdf = PDDocument.load(new URL(acrobatSourceUrl).openStream());
+            PDDocumentCatalog catalog = testPdf.getDocumentCatalog();
+            PDAcroForm acroForm = catalog.getAcroForm(null);
+            numFormFieldsByAcrobat = acroForm.getFields().size();
+            for (PDField field : acroForm.getFieldTree())
+            {
+                fieldsByName.put(field.getFullyQualifiedName(), field);
+            }
+        }
+        finally
+        {
+            IOUtils.closeQuietly(testPdf);
+        }
+
+        try
+        {
+            testPdf = PDDocument.load(new URL(sourceUrl).openStream());
+            PDDocumentCatalog catalog = testPdf.getDocumentCatalog();
+            // need to do a low level cos access as the PDModel access will build the AcroForm
+            COSDictionary cosAcroForm = (COSDictionary) catalog.getCOSObject().getDictionaryObject(COSName.ACRO_FORM);
+            COSArray cosFields = (COSArray) cosAcroForm.getDictionaryObject(COSName.FIELDS);
+            assertEquals("Initially there shall be 0 fields", 0, cosFields.size());
+            PDAcroForm acroForm = catalog.getAcroForm(new CreateFieldsFixup(testPdf));
+            assertEquals("After rebuild there shall be " + numFormFieldsByAcrobat + " fields", numFormFieldsByAcrobat, acroForm.getFields().size());
+
+            // the the fields found are contained in the map
+            for (PDField field : acroForm.getFieldTree())
+            {
+                assertNotNull(fieldsByName.get(field.getFullyQualifiedName()));
+            }
+
+            // test all fields in the map are also found in the AcroForm
+            for (String fieldName : fieldsByName.keySet())
+            {
+                assertNotNull(acroForm.getField(fieldName));
+            }
+        }
+        finally
+        {
+            IOUtils.closeQuietly(testPdf);
+        }
+    }
+
+    /*
+     * Create fields from widget annotations
+     */
+    class CreateFieldsFixup extends AbstractFixup
+    {
+        CreateFieldsFixup(PDDocument document)
+        { 
+            super(document); 
+        }
+
+        @Override
+        public void apply() {
+            new AcroFormOrphanWidgetsProcessor(document).process();
+
+        }        
+    }
 }