Author: lehmi
Date: Thu Oct  3 17:17:33 2024
New Revision: 1921096

URL: http://svn.apache.org/viewvc?rev=1921096&view=rev
Log:
PDFBOX-4718: optimize intersection of clipping paths

Modified:
    
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/state/PDGraphicsState.java

Modified: 
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/state/PDGraphicsState.java
URL: 
http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/state/PDGraphicsState.java?rev=1921096&r1=1921095&r2=1921096&view=diff
==============================================================================
--- 
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/state/PDGraphicsState.java
 (original)
+++ 
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/state/PDGraphicsState.java
 Thu Oct  3 17:17:33 2024
@@ -21,10 +21,9 @@ import java.awt.Composite;
 import java.awt.geom.Area;
 import java.awt.geom.GeneralPath;
 import java.awt.geom.Path2D;
+import java.awt.geom.Rectangle2D;
 import java.util.ArrayList;
-import java.util.IdentityHashMap;
 import java.util.List;
-import java.util.Map;
 
 import org.apache.pdfbox.cos.COSBase;
 
@@ -46,7 +45,7 @@ public class PDGraphicsState implements
 {
     private boolean isClippingPathDirty;
     private List<Path2D> clippingPaths = new ArrayList<>(1);
-    private Map<Path2D, Area> clippingCache = new IdentityHashMap<>();
+    private Area clippingPathCache = null;
     private Matrix currentTransformationMatrix = new Matrix();
     private PDColor strokingColor = PDDeviceGray.INSTANCE.getInitialColor();
     private PDColor nonStrokingColor = PDDeviceGray.INSTANCE.getInitialColor();
@@ -488,7 +487,7 @@ public class PDGraphicsState implements
             clone.nonStrokingColor = nonStrokingColor; // immutable
             clone.lineDashPattern = lineDashPattern; // immutable
             clone.clippingPaths = clippingPaths; // not cloned, see 
intersectClippingPath
-            clone.clippingCache = clippingCache;
+            clone.clippingPathCache = clippingPathCache;
             clone.isClippingPathDirty = false;
             clone.textLineMatrix = textLineMatrix == null ? null : 
textLineMatrix.clone();
             clone.textMatrix = textMatrix == null ? null : textMatrix.clone();
@@ -597,12 +596,12 @@ public class PDGraphicsState implements
         {
             // shallow copy
             clippingPaths = new ArrayList<>(clippingPaths);
-
             isClippingPathDirty = true;
         }
-
         // add path to current clipping paths, combined later (see 
getCurrentClippingPath)
         clippingPaths.add(clonePath ? (Path2D) path.clone() : path);
+        // clear cache
+        clippingPathCache = null;
     }
 
     /**
@@ -622,24 +621,40 @@ public class PDGraphicsState implements
      */
     public Area getCurrentClippingPath()
     {
+        // If there is just a single clipping path, no intersections are 
needed.
         if (clippingPaths.size() == 1)
         {
-            // If there is just a single clipping path, no intersections are 
needed.
-            Path2D path = clippingPaths.get(0);
-            return clippingCache.computeIfAbsent(path, Area::new);
+            if (clippingPathCache == null)
+            {
+                clippingPathCache = new Area(clippingPaths.get(0));
+            }
+            return clippingPathCache;
         }
-        // If there are multiple clipping paths, combine them to a single area.
-        Area clippingArea = new Area();
-        clippingArea.add(new Area(clippingPaths.get(0)));
+        // calculate the intersected overall bounding box for all clipping 
paths
+        Rectangle2D boundingBox = clippingPaths.get(0).getBounds2D();
         for (int i = 1; i < clippingPaths.size(); i++)
         {
-            clippingArea.intersect(new Area(clippingPaths.get(i)));
+            Rectangle2D.intersect(boundingBox, 
clippingPaths.get(i).getBounds2D(), boundingBox);
+        }
+        // use the overall bounding box as starting area
+        Area clippingArea = new Area(boundingBox);
+        // combine all clipping paths to a single area
+        for (int i = 0; i < clippingPaths.size(); i++)
+        {
+            Area nextArea = new Area(clippingPaths.get(i));
+            // skip rectangular areas as they were already taken into account 
when calculating the overall bounding box
+            if (nextArea.isRectangular())
+            {
+                nextArea.reset();
+                continue;
+            }
+            clippingArea.intersect(nextArea);
+            nextArea.reset();
         }
-        // Replace the list of individual clipping paths with the 
intersection, and add it to the cache.
-        Path2D newPath = new Path2D.Double(clippingArea);
+        clippingPathCache = clippingArea;
+        // Replace the list of individual clipping paths with the intersection
         clippingPaths = new ArrayList<>(1);
-        clippingPaths.add(newPath);
-        clippingCache.put(newPath, clippingArea);
+        clippingPaths.add(new Path2D.Double(clippingArea));
         return clippingArea;
     }
 


Reply via email to