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 2021/08/14 13:17:39 UTC

svn commit: r1892338 - in /pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox: pdmodel/graphics/state/PDGraphicsState.java rendering/PageDrawer.java

Author: tilman
Date: Sat Aug 14 13:17:39 2021
New Revision: 1892338

URL: http://svn.apache.org/viewvc?rev=1892338&view=rev
Log:
PDFBOX-5258: lazy clipping, by Stephan Schwiebert; closes #127

Modified:
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/state/PDGraphicsState.java
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/rendering/PageDrawer.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=1892338&r1=1892337&r2=1892338&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 Sat Aug 14 13:17:39 2021
@@ -20,6 +20,12 @@ import java.awt.BasicStroke;
 import java.awt.Composite;
 import java.awt.geom.Area;
 import java.awt.geom.GeneralPath;
+import java.awt.geom.Path2D;
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+
 import org.apache.pdfbox.cos.COSBase;
 
 import org.apache.pdfbox.pdmodel.common.PDRectangle;
@@ -39,7 +45,8 @@ import org.apache.pdfbox.util.Matrix;
 public class PDGraphicsState implements Cloneable
 {
     private boolean isClippingPathDirty;
-    private Area clippingPath;
+    private List<Path2D.Double> clippingPaths = new ArrayList<>();
+    private Map<Path2D.Double, Area> clippingCache = new IdentityHashMap<>();
     private Matrix currentTransformationMatrix = new Matrix();
     private PDColor strokingColor = PDDeviceGray.INSTANCE.getInitialColor();
     private PDColor nonStrokingColor = PDDeviceGray.INSTANCE.getInitialColor();
@@ -76,7 +83,7 @@ public class PDGraphicsState implements
      */
     public PDGraphicsState(PDRectangle page)
     {
-        clippingPath = new Area(page.toGeneralPath());
+        clippingPaths.add(new Path2D.Double(page.toGeneralPath()));
     }
 
     /**
@@ -473,7 +480,8 @@ public class PDGraphicsState implements
             clone.strokingColor = strokingColor; // immutable
             clone.nonStrokingColor = nonStrokingColor; // immutable
             clone.lineDashPattern = lineDashPattern; // immutable
-            clone.clippingPath = clippingPath; // not cloned, see intersectClippingPath
+            clone.clippingPaths = clippingPaths; // not cloned, see intersectClippingPath
+            clone.clippingCache = clippingCache;
             clone.isClippingPathDirty = false;
             return clone;
         }
@@ -570,28 +578,32 @@ public class PDGraphicsState implements
      */
     public void intersectClippingPath(GeneralPath path)
     {
-        intersectClippingPath(new Area(path));
+        intersectClippingPath(new Path2D.Double(path), true);
     }
 
-    /**
-     * Modify the current clipping path by intersecting it with the given path.
-     * @param area area to intersect with the clipping path
-     */
-    public void intersectClippingPath(Area area)
+    private void intersectClippingPath(Path2D.Double path, boolean clonePath)
     {
         // lazy cloning of clipping path for performance
         if (!isClippingPathDirty)
         {
-            // deep copy (can't use clone() as it performs only a shallow copy)
-            Area cloned = new Area();
-            cloned.add(clippingPath);
-            clippingPath = cloned;
+            // shallow copy
+            clippingPaths = new ArrayList<>(clippingPaths);
 
             isClippingPathDirty = true;
         }
 
-        // intersection as usual
-        clippingPath.intersect(area);
+        // add path to current clipping paths, combined later (see getCurrentClippingPath)
+        clippingPaths.add(clonePath ? (Path2D.Double) path.clone() : path);
+    }
+
+    /**
+     * Modify the current clipping path by intersecting it with the given path.
+     *
+     * @param area area to intersect with the clipping path
+     */
+    public void intersectClippingPath(Area area)
+    {
+        intersectClippingPath(new Path2D.Double(area), false);
     }
 
     /**
@@ -601,7 +613,35 @@ public class PDGraphicsState implements
      */
     public Area getCurrentClippingPath()
     {
-        return clippingPath;
+        if (clippingPaths.size() == 1)
+        {
+            // If there is just a single clipping path, no intersections are needed.
+            Path2D.Double path = clippingPaths.get(0);
+            return clippingCache.computeIfAbsent(path, Area::new);
+        }
+        // If there are multiple clipping paths, combine them to a single area.
+        Area clippingArea = new Area();
+        clippingArea.add(new Area(clippingPaths.get(0)));
+        for (int i = 1; i < clippingPaths.size(); i++)
+        {
+            clippingArea.intersect(new Area(clippingPaths.get(i)));
+        }
+        // Replace the list of individual clipping paths with the intersection, and add it to the cache.
+        Path2D.Double newPath = new Path2D.Double(clippingArea);
+        clippingPaths = new ArrayList<>();
+        clippingPaths.add(newPath);
+        clippingCache.put(newPath, clippingArea);
+        return clippingArea;
+    }
+
+    /**
+     * This will get the current clipping path, as one or more individual paths. Do not modify the list or the paths!
+     *
+     * @return The current clipping paths.
+     */
+    public List<Path2D.Double> getCurrentClippingPaths()
+    {
+        return clippingPaths;
     }
 
     public Composite getStrokingJavaComposite()

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/rendering/PageDrawer.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/rendering/PageDrawer.java?rev=1892338&r1=1892337&r2=1892338&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/rendering/PageDrawer.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/rendering/PageDrawer.java Sat Aug 14 13:17:39 2021
@@ -140,7 +140,7 @@ public class PageDrawer extends PDFGraph
     private GeneralPath linePath = new GeneralPath();
     
     // last clipping path
-    private Area lastClip;
+    private List<Path2D.Double> lastClips;
 
     // clip when drawPage() is called, can be null, must be intersected when clipping
     private Shape initialClip;
@@ -293,8 +293,8 @@ public class PageDrawer extends PDFGraph
         int savedClipWindingRule = clipWindingRule;
         clipWindingRule = -1;
 
-        Area savedLastClip = lastClip;
-        lastClip = null;
+        List<Path2D.Double> savedLastClips = lastClips;
+        lastClips = null;
         Shape savedInitialClip = initialClip;
         initialClip = null;
         
@@ -307,7 +307,7 @@ public class PageDrawer extends PDFGraph
         flipTG = savedFlipTG;
         graphics = savedGraphics;
         linePath = savedLinePath;
-        lastClip = savedLastClip;
+        lastClips = savedLastClips;
         initialClip = savedInitialClip;
         clipWindingRule = savedClipWindingRule;
     }
@@ -382,25 +382,40 @@ public class PageDrawer extends PDFGraph
      */
     protected final void setClip()
     {
-        Area clippingPath = getGraphicsState().getCurrentClippingPath();
-        if (clippingPath != lastClip)
+        List<Path2D.Double> clippingPaths = getGraphicsState().getCurrentClippingPaths();
+        if (clippingPaths != lastClips)
         {
-            if (clippingPath.getPathIterator(null).isDone())
-            {
-                // PDFBOX-4821: avoid bug with java printing that empty clipping path is ignored by
-                // replacing with empty rectangle, works because this is not an empty path
-                graphics.setClip(new Rectangle());
-            }
-            else
-            {
-                graphics.setClip(clippingPath);
-            }
+            transferClip(getGraphicsState(), graphics);
             if (initialClip != null)
             {
                 // apply the remembered initial clip, but transform it first
                 //TODO see PDFBOX-4583
             }
-            lastClip = clippingPath;
+            lastClips = clippingPaths;
+        }
+    }
+
+    /**
+     * Transfer clip to the destination device. Override this if you want to avoid to do slow
+     * intersecting operations but want the destination device to do this (e.g. SVG). You can get
+     * the individual clippings with {@link PDGraphicsState#getCurrentClippingPaths()}. See
+     * <a href="https://issues.apache.org/jira/browse/PDFBOX-5258">PDFBOX-5258</a> for sample code.
+     *
+     * @param graphicsState
+     * @param graphics graphics device
+     */
+    protected void transferClip(PDGraphicsState graphicsState, Graphics2D graphics)
+    {
+        Area clippingPath = graphicsState.getCurrentClippingPath();
+        if (clippingPath.getPathIterator(null).isDone())
+        {
+            // PDFBOX-4821: avoid bug with java printing that empty clipping path is ignored by
+            // replacing with empty rectangle, works because this is not an empty path
+            graphics.setClip(new Rectangle());
+        }
+        else
+        {
+            graphics.setClip(clippingPath);
         }
     }
 
@@ -449,7 +464,7 @@ public class PageDrawer extends PDFGraph
 
             // PDFBOX-3681: lastClip needs to be reset, because after intersection it is still the same 
             // object, thus setClip() would believe that it is cached.
-            lastClip = null;
+            lastClips = null;
         }
     }
 
@@ -995,7 +1010,7 @@ public class PageDrawer extends PDFGraph
 
             // PDFBOX-3836: lastClip needs to be reset, because after intersection it is still the same 
             // object, thus setClip() would believe that it is cached.
-            lastClip = null;
+            lastClips = null;
 
             clipWindingRule = -1;
         }
@@ -1366,7 +1381,7 @@ public class PageDrawer extends PDFGraph
         graphics.setComposite(getGraphicsState().getNonStrokingJavaComposite());
         graphics.setPaint(paint);
         graphics.setClip(null);
-        lastClip = null;
+        lastClips = null;
 
         // get the transformed BBox and intersect with current clipping path
         // need to do it here and not in shading getRaster() because it may have been rotated
@@ -1403,7 +1418,7 @@ public class PageDrawer extends PDFGraph
     @Override
     public void showAnnotation(PDAnnotation annotation) throws IOException
     {
-        lastClip = null;
+        lastClips = null;
         int deviceType = -1;
         GraphicsConfiguration graphicsConfiguration = graphics.getDeviceConfiguration();
         if (graphicsConfiguration != null)
@@ -1592,7 +1607,7 @@ public class PageDrawer extends PDFGraph
                 PDColor backdropColor) throws IOException
         {
             Graphics2D savedGraphics = graphics;
-            Area savedLastClip = lastClip;
+            List<Path2D.Double> savedLastClips = lastClips;
             Shape savedInitialClip = initialClip;
 
             // get the CTM x Form Matrix transform
@@ -1740,7 +1755,7 @@ public class PageDrawer extends PDFGraph
             finally 
             {
                 flipTG = savedFlipTG;
-                lastClip = savedLastClip;
+                lastClips = savedLastClips;
                 graphics.dispose();
                 graphics = savedGraphics;
                 initialClip = savedInitialClip;