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;