]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
FOP-1722: Values in PDF Number Trees must be indirect references by Dave Roxburgh
authorSimon Steiner <ssteiner@apache.org>
Wed, 29 Mar 2023 14:49:27 +0000 (15:49 +0100)
committerSimon Steiner <ssteiner@apache.org>
Wed, 29 Mar 2023 14:49:27 +0000 (15:49 +0100)
fop-core/src/main/java/org/apache/fop/pdf/PDFNumsArray.java
fop-core/src/main/java/org/apache/fop/render/pdf/PDFLogicalStructureHandler.java
fop-core/src/test/java/org/apache/fop/pdf/PDFLibraryTestSuite.java
fop-core/src/test/java/org/apache/fop/pdf/PDFNumsArrayTestCase.java
fop-core/src/test/java/org/apache/fop/pdf/PDFParentTreeTestCase.java

index e9e1855b0a29041d0c7d8f9a0ed83429edf52c7f..aafff37fcd0100e52417e2f45634266bac38b395 100644 (file)
@@ -50,13 +50,83 @@ public class PDFNumsArray extends PDFObject {
         return this.map.size();
     }
 
+    /**
+     * Determines whether a value object should be converted to an indirect reference for inclusion in a Number Tree
+     * array according to the PDF spec.
+     * PDF1.0 - 1.2 - Spec is silent on this subject (as Number Trees don't exist).
+     * PDF1.3 & 1.4 - Values must be indirect object refs.
+     * PDF1.5       - Recommended: stream, dictionary, array, and string values be indirect object refs.
+     * PDF1.6 - 2.0 - Stream values must be indirect object refs.
+     *                Recommended: dictionary, array, and string values be indirect object refs.
+     * Method signals for values that must be, and those recommended to be indirect object refs.
+     * @param obj The object to be considered.
+     * @return True iff the object should be converted.
+     */
+    private boolean shouldConvertToRef(PDFObject obj) {
+        boolean retval = false;
+        if (getDocument() != null && getDocument().getPDFVersion() != null) {
+            switch (getDocument().getPDFVersion()) {
+                case V1_0: // fall-through
+                case V1_1: // fall-through
+                case V1_2:
+                    log.error("Number Tree used in PDF version " + getDocument().getPDFVersion());
+                    break;
+                case V1_3: // fall-through
+                case V1_4:
+                    retval = true;
+                    break;
+                case V1_5: // fall-through
+                case V1_6: // fall-through
+                case V1_7: // fall-through
+                case V2_0:
+                    if (obj instanceof PDFStream
+                            || obj instanceof PDFDictionary
+                            || obj instanceof PDFArray
+                            || obj instanceof PDFText) {
+                        retval = true;
+                    }
+                    break;
+                default:
+                    log.error("Unrecognised PDF version " + getDocument().getPDFVersion());
+                    break;
+            }
+        }
+        return retval;
+    }
+
+    /**
+     * This method provides conformance with the different PDF specs which require or recommend different types be used
+     * for Number Tree array values. Method indirects objects where indicated.
+     * @param obj The object to be considered for indirection.
+     * @return Either the object or a reference to the object.
+     */
+    private Object indirectIfReq(Object obj) {
+        PDFDocument doc = getDocument();
+        Object retval = obj;
+        if (obj instanceof PDFObject) {
+            PDFObject pdfObj = (PDFObject) obj;
+            PDFObject parent = pdfObj.getParent();
+            if (shouldConvertToRef(pdfObj)) {
+                if (!pdfObj.hasObjectNumber()) { // Needs registering with the doc.
+                    pdfObj.setParent(null); // Can't register if it has a parent.
+                    pdfObj = doc.registerObject(pdfObj);
+                    if (parent != null) {
+                        pdfObj.setParent(parent); // Reinstate original parent.
+                    }
+                }
+                retval = pdfObj.makeReference();
+            }
+        }
+        return retval;
+    }
+
     /**
      * Sets an entry.
      * @param key the key of the value to set
      * @param obj the new value
      */
     public void put(Integer key, Object obj) {
-        this.map.put(key, obj);
+        this.map.put(key, indirectIfReq(obj));
     }
 
     /**
index 318892ba37e791471efad5a3bb07ca22f805eac2..12bb7a85c9d80e5da726e1d5a103666df8b094e5 100644 (file)
@@ -119,14 +119,6 @@ public class PDFLogicalStructureHandler {
      * Receive notification of the end of the current page.
      */
     void endPage() {
-        // TODO
-        // Values in a number tree must be indirect references to the PDF
-        // objects associated to the keys. To enforce that the array is
-        // registered to the PDF document. Unfortunately that can't be done
-        // earlier since a call to PDFContentGenerator.flushPDFDoc can be made
-        // before the array is complete, which would result in only part of it
-        // being output to the PDF.
-        // This should really be handled by PDFNumsArray
         pdfDoc.registerObject(pageParentTreeArray);
         parentTree.addToNums(currentPage.getStructParents(), pageParentTreeArray);
     }
index 2f7fccc3af4ca40f4c6a934a4f4a802ff6fdf913..374540355db3db0c287ecaf14da0500cbfbb9716 100644 (file)
@@ -40,6 +40,7 @@ import org.apache.fop.render.pdf.extensions.PDFEmbeddedFileAttachmentTestCase;
         PDFDocumentTestCase.class,
         PDFNullTestCase.class,
         PDFNumsArrayTestCase.class,
+        PDFNumsArrayPDFV17TestCase.class,
         PDFRectangleTestCase.class,
         PDFReferenceTestCase.class,
         PDFResourcesTestCase.class,
index d36775cdac42b5a4655be2d03ddc5cf3d28dadb9..035912a9535d89186116c63bc8d604f14d1e5286 100644 (file)
@@ -25,20 +25,25 @@ import org.junit.Before;
 import org.junit.Test;
 
 /**
- * Test case for {@link PDFNumsArray}.
+ * Test case for {@link PDFNumsArray}. Uses default PDF doc (version 1.4) - requires text values and number values to be
+ * included as refs.
  */
 public class PDFNumsArrayTestCase extends PDFObjectTestCase {
+    private static final String TEST_NAME = "Test name";
+    private static final int TEST_NUMBER = 10;
     private PDFNumsArray numsArray;
-    private String expectedString = "[0 /Test#20name 1 10]";
+    private String expectedString = "[0 1 0 R 1 2 0 R]";
+    private String expectedStringName = "/Test#20name";
+    private String expectedStringNumber = Integer.valueOf(TEST_NUMBER).toString();
 
     @Before
     public void setUp() {
         numsArray = new PDFNumsArray(parent);
-        numsArray.put(0, new PDFName("Test name"));
+        numsArray.setDocument(doc);
+        numsArray.put(0, new PDFName(TEST_NAME));
         PDFNumber num = new PDFNumber();
-        num.setNumber(10);
+        num.setNumber(TEST_NUMBER);
         numsArray.put(1, num);
-        numsArray.setDocument(doc);
 
         pdfObjectUnderTest = numsArray;
     }
@@ -50,5 +55,7 @@ public class PDFNumsArrayTestCase extends PDFObjectTestCase {
     @Test
     public void testOutput() throws IOException {
         testOutputStreams(expectedString, numsArray);
+        testOutputStreams(expectedStringName, doc.objects.get(1));
+        testOutputStreams(expectedStringNumber, doc.objects.get(2));
     }
 }
index 6fa5d6a42dc20c1b8459026952363bcb8a32608e..acb888e4e96a1a78736ebb146c49bac5eace69c3 100644 (file)
@@ -24,7 +24,7 @@ import org.junit.Test;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
 
 /**
  * Tests that the nums array in the ParentTree dictionary is correctly being split into
@@ -76,7 +76,7 @@ public class PDFParentTreeTestCase {
      */
     @Test
     public void testOutOfOrderSplit() throws Exception {
-        PDFStructElem structElem = mock(PDFStructElem.class);
+        PDFStructElem structElem = spy(PDFStructElem.class);
         for (int num = 50; num < 53; num++) {
             parentTree.addToNums(num, structElem);
         }
@@ -98,7 +98,7 @@ public class PDFParentTreeTestCase {
      * @throws Exception
      */
     private int getArrayNumber(int elementNumber) throws Exception {
-        PDFStructElem structElem = mock(PDFStructElem.class);
+        PDFStructElem structElem = spy(PDFStructElem.class);
         for (int structParent = 0; structParent < elementNumber; structParent++) {
             parentTree.addToNums(structParent, structElem);
         }