You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pdfbox.apache.org by ti...@apache.org on 2026/10/17 13:51:09 UTC

svn commit: r1939194 - pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/multipdf

Author: tilman
Date: Sat Oct 17 13:51:09 2026
New Revision: 1939194

Log:
PDFBOX-6283: optimize fixing named destinations name tree; keep named destination except in case with broken string

Modified:
   pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/multipdf/Splitter.java

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/multipdf/Splitter.java
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/multipdf/Splitter.java	Sat Oct 17 13:17:45 2026	(r1939193)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/multipdf/Splitter.java	Sat Oct 17 13:51:09 2026	(r1939194)
@@ -35,6 +35,7 @@ import org.apache.pdfbox.cos.COSDictiona
 import org.apache.pdfbox.cos.COSInteger;
 import org.apache.pdfbox.cos.COSName;
 import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.cos.COSString;
 import org.apache.pdfbox.io.RandomAccessStreamCache.StreamCacheCreateFunction;
 import org.apache.pdfbox.pdmodel.PDDestinationNameTreeNode;
 import org.apache.pdfbox.pdmodel.PDDocument;
@@ -87,6 +88,7 @@ public class Splitter
     private List<PDDocument> destinationDocuments;
     private Map<COSDictionary, COSDictionary> pageDictMap; // map old page => new page for the current destination document
     private List<Map<COSDictionary, COSDictionary>> pageDictMaps; // list of these maps for all destination documents
+    private Map<PDPage,Integer> pageToDocNumMap; // map old page => index of destination document
     private Map<COSDictionary, COSDictionary> structDictMap;
     private List<Map<COSDictionary, COSDictionary>> annotDictMaps; // map old annotation => new annotation for the current destination document
     private Map<COSDictionary, COSDictionary> annotDictMap; // list of these maps for all destination documents
@@ -135,6 +137,7 @@ public class Splitter
         destinationDocuments = new ArrayList<>();
         sourceDocument = document;
         pageDictMaps = new ArrayList<>();
+        pageToDocNumMap = new HashMap<>();
         annotDictMaps = new ArrayList<>();
         destToFixMap = new HashMap<>();
         idSet = new HashSet<>();
@@ -150,11 +153,81 @@ public class Splitter
             cloneStructureTree(destinationDocument);
             fixDestinations(destinationDocument);
         }
-
+        fixNameDestinations();
         return destinationDocuments;
     }
     
     /**
+     * PDFBOX-6283: Fix the destinations in /Root/Names/Dests by creating clones only for
+     * the destinations that are in the destination documents.
+     *
+     * @throws IOException 
+     */
+    void fixNameDestinations() throws IOException
+    {
+        PDDocumentNameDictionary srcNames = sourceDocument.getDocumentCatalog().getNames();
+        if (srcNames == null)
+        {
+            return;
+        }
+        PDDestinationNameTreeNode srcDests = srcNames.getDests();
+        if (srcDests == null)
+        {
+            return;
+        }
+
+        List<Map<String, PDPageDestination>> dstTreeAsMapList =
+                new ArrayList<>(destinationDocuments.size());
+        for (int i = 0; i < destinationDocuments.size(); ++i)
+        {
+            dstTreeAsMapList.add(new HashMap<>());
+        }
+
+        // map all named destinations to the pages in the new documents
+        Map<String, PDPageDestination> srcTreeAsMap = srcDests.getTreeAsMap();
+        for (Map.Entry<String, PDPageDestination> entry : srcTreeAsMap.entrySet())
+        {
+            PDPageDestination value = entry.getValue();
+            if (value == null)
+            {
+                // happens with file 000671.pdf, entry "Raff1"
+                continue;
+            }
+            PDPage pageInTreeDest = value.getPage();
+            // find out in which destination document this should be added
+            int docIndex = pageToDocNumMap.get(pageInTreeDest);
+            pageDictMap = pageDictMaps.get(docIndex); // get appropriate mapping
+            COSDictionary dstPageDict = pageDictMap.get(pageInTreeDest.getCOSObject());
+
+            // clone destination
+            COSArray clonedDestinationArray = new COSArray(value.getCOSObject().toList());
+            PDPageDestination dstDestination = (PDPageDestination) PDDestination.create(clonedDestinationArray);
+            dstDestination.setPage(new PDPage(dstPageDict));
+
+            // add to the treeMap of the appropriate destination document
+            Map<String, PDPageDestination> dstTreeAsMap = dstTreeAsMapList.get(docIndex);
+            dstTreeAsMap.put(entry.getKey(), dstDestination);
+        }
+
+        // Assign all non empty treemaps to the new documents
+        for (int i = 0; i < destinationDocuments.size(); ++i)
+        {
+            Map<String, PDPageDestination> dstTreeAsMap = dstTreeAsMapList.get(i);
+            if (dstTreeAsMap.isEmpty())
+            {
+                continue;
+            }
+            PDDocument destinationDocument = destinationDocuments.get(i);
+            PDDocumentCatalog dstDocumentCatalog = destinationDocument.getDocumentCatalog();
+            PDDocumentNameDictionary dstDocumentNameDictionary = new PDDocumentNameDictionary(dstDocumentCatalog);
+            PDDestinationNameTreeNode dstDests = new PDDestinationNameTreeNode();
+            dstDocumentNameDictionary.setDests(dstDests);
+            dstDocumentCatalog.setNames(dstDocumentNameDictionary);
+            dstDests.setNames(dstTreeAsMap);            
+        }
+    }
+
+    /**
      * Replace the page destinations, if the source and destination pages are in the target
      * document. This must be called after all pages (and its annotations) are processed.
      *
@@ -822,55 +895,7 @@ public class Splitter
         processAnnotations(imported);
 
         pageDictMap.put(page.getCOSObject(), imported.getCOSObject());
-
-        //TODO this segment belongs within fixDestinations() using the pageDictMap
-        // this will prevent some multiple work
-        // Find named destinations that are in the destination document
-        PDDocumentNameDictionary names = sourceDocument.getDocumentCatalog().getNames();
-        if (names != null)
-        {
-            PDDestinationNameTreeNode dests = names.getDests();
-            if (dests != null)
-            {
-                Map<String, PDPageDestination> srcTreeAsMap = dests.getTreeAsMap();
-
-                // create destination structure if needed (may have been done for a previous page)
-                PDDocumentCatalog dstDocumentCatalog = getDestinationDocument().getDocumentCatalog();
-                PDDocumentNameDictionary dstNames = dstDocumentCatalog.getNames();
-                if (dstNames == null)
-                {
-                    dstNames = new PDDocumentNameDictionary(dstDocumentCatalog);
-                }
-                PDDestinationNameTreeNode dstDests = dstNames.getDests();
-                if (dstDests == null)
-                {
-                    dstDests = new PDDestinationNameTreeNode();
-                }
-                Map<String, PDPageDestination> dstTreeAsMap = dstDests.getTreeAsMap();
-                
-                for (Map.Entry<String, PDPageDestination> entry : srcTreeAsMap.entrySet())
-                {
-                    PDPageDestination value = entry.getValue();
-                    PDPage pageInTreeDest = value.getPage();
-                    if (page.equals(pageInTreeDest))
-                    {
-                        // clone destination
-                        COSArray clonedDestinationArray =
-                                new COSArray(value.getCOSObject().toList());
-                        PDPageDestination dstDestination =
-                                (PDPageDestination) PDDestination.create(clonedDestinationArray);
-                        dstDestination.setPage(imported);
-                        dstTreeAsMap.put(entry.getKey(), dstDestination);
-                    }
-                }
-                if (!dstTreeAsMap.isEmpty())
-                {
-                    dstDests.setNames(dstTreeAsMap);
-                    dstNames.setDests(dstDests);
-                    dstDocumentCatalog.setNames(dstNames);
-                }
-            }
-        }
+        pageToDocNumMap.put(page, destinationDocuments.size() - 1);
     }
 
     /**
@@ -928,11 +953,15 @@ public class Splitter
                 }
                 if (srcDestination instanceof PDNamedDestination)
                 {
-                    srcDestination = sourceDocument.getDocumentCatalog().
-                            findNamedDestinationPage((PDNamedDestination) srcDestination);
-                    // we do not use the named destination anymore because names get modified, e.g.
-                    // 0xAD becomes 0, see file 410609.pdf where the name no longer matches with the
-                    // entry in the new name tree; plus the original solution was 40 additional loc
+                    // PDFBOX-5840 / PDFBOX-6283: In a few cases, e.g. in PDFBOX-5840-410609.pdf
+                    // 0xAD becomes 0 so the name would no longer match the entry in the new
+                    // name tree, so we replace the name with the actual destination.
+                    COSString cosString = (COSString) srcDestination.getCOSObject();
+                    if (cosString.toString().contains("\000"))
+                    {
+                        srcDestination = sourceDocument.getDocumentCatalog().
+                                findNamedDestinationPage((PDNamedDestination) srcDestination);
+                    }
                 }
                 if (srcDestination instanceof PDPageDestination)
                 {