From 6d6f7deaf48215d2a7cdf196863505defe9a3789 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Lehmk=C3=BChler?= Date: Thu, 3 Oct 2024 17:17:33 +0000 Subject: [PATCH] PDFBOX-4718: optimize intersection of clipping paths git-svn-id: https://svn.apache.org/repos/asf/pdfbox/trunk@1921096 13f79535-47bb-0310-9956-ffa450edef68 --- .../graphics/state/PDGraphicsState.java | 49 ++++++++++++------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/state/PDGraphicsState.java b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/state/PDGraphicsState.java index fa20d73b94d..b20de51f1bb 100644 --- a/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/state/PDGraphicsState.java +++ b/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/graphics/state/PDGraphicsState.java @@ -21,10 +21,9 @@ 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 Cloneable { private boolean isClippingPathDirty; private List clippingPaths = new ArrayList<>(1); - private Map 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 PDGraphicsState clone() 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 @@ private void intersectClippingPath(Path2D path, boolean clonePath) { // 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 void intersectClippingPath(Area area) */ 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; }