@@ -95,10 +95,13 @@ class PdfCleanUpFilter {
9595 private static final Set <PdfName > NOT_SUPPORTED_FILTERS_FOR_DIRECT_CLEANUP = Collections .unmodifiableSet (
9696 new LinkedHashSet <>(Arrays .asList (PdfName .JBIG2Decode , PdfName .DCTDecode , PdfName .JPXDecode )));
9797
98- private List <Rectangle > regions ;
98+ private final List <Rectangle > regions ;
9999
100- public PdfCleanUpFilter (List <Rectangle > regions ) {
100+ private final CleanUpProperties properties ;
101+
102+ public PdfCleanUpFilter (List <Rectangle > regions , CleanUpProperties properties ) {
101103 this .regions = regions ;
104+ this .properties = properties ;
102105 }
103106
104107 static boolean imageSupportsDirectCleanup (PdfImageXObject image ) {
@@ -118,7 +121,7 @@ static boolean imageSupportsDirectCleanup(PdfImageXObject image) {
118121 * are never considered as intersecting.
119122 * @return true if the rectangles intersect, false otherwise
120123 */
121- static boolean checkIfRectanglesIntersect (Point [] rect1 , Point [] rect2 ) {
124+ boolean checkIfRectanglesIntersect (Point [] rect1 , Point [] rect2 ) {
122125 IClipper clipper = new DefaultClipper ();
123126 // If the redaction area is degenerate, the result will be false
124127 if (!ClipperBridge .addPolygonToClipper (clipper , rect2 , PolyType .CLIP )) {
@@ -170,29 +173,44 @@ static boolean checkIfRectanglesIntersect(Point[] rect1, Point[] rect2) {
170173 // working with paths is considered to be a bit faster in terms of performance.
171174 Paths paths = new Paths ();
172175 clipper .execute (ClipType .INTERSECTION , paths , PolyFillType .NON_ZERO , PolyFillType .NON_ZERO );
173- return !checkIfIntersectionRectangleDegenerate (paths .getBounds (), false )
174- && !paths .isEmpty ();
175- } else {
176- int rect1Size = rect1 .length ;
176+ return checkIfIntersectionOccurs (paths , rect1 , false );
177+ }
178+ intersectionSubjectAdded = ClipperBridge .addPolylineSubjectToClipper (clipper , rect1 );
179+ if (!intersectionSubjectAdded ) {
180+ // According to the comment above,
181+ // this could have happened only if all four passed points are actually the same point.
182+ // Adding here a point really close to the original point, to make sure it's not covered by the
183+ // intersecting rectangle.
184+ final double SMALL_DIFF = 0.01 ;
185+ final Point [] expandedRect1 = new Point [rect1 .length + 1 ];
186+ System .arraycopy (rect1 , 0 , expandedRect1 , 0 , rect1 .length );
187+ expandedRect1 [rect1 .length ] = new Point (rect1 [0 ].getX () + SMALL_DIFF , rect1 [0 ].getY ());
188+ rect1 = expandedRect1 ;
189+
177190 intersectionSubjectAdded = ClipperBridge .addPolylineSubjectToClipper (clipper , rect1 );
178- if (!intersectionSubjectAdded ) {
179- // According to the comment above,
180- // this could have happened only if all four passed points are actually the same point.
181- // Adding here a point really close to the original point, to make sure it's not covered by the
182- // intersecting rectangle.
183- double smallDiff = 0.01 ;
184- List <Point > rect1List = new ArrayList <Point >(Arrays .asList (rect1 ));
185- rect1List .add (new Point (rect1 [0 ].getX () + smallDiff , rect1 [0 ].getY ()));
186- rect1 = rect1List .toArray (new Point [rect1Size ]);
187- intersectionSubjectAdded = ClipperBridge .addPolylineSubjectToClipper (clipper , rect1 );
188- assert intersectionSubjectAdded ;
189- }
190- PolyTree polyTree = new PolyTree ();
191- clipper .execute (ClipType .INTERSECTION , polyTree , PolyFillType .NON_ZERO , PolyFillType .NON_ZERO );
192- Paths paths = Paths .makePolyTreeToPaths (polyTree );
193- return !checkIfIntersectionRectangleDegenerate (paths .getBounds (), true )
194- && !paths .isEmpty ();
191+ assert intersectionSubjectAdded ;
192+ }
193+ PolyTree polyTree = new PolyTree ();
194+ clipper .execute (ClipType .INTERSECTION , polyTree , PolyFillType .NON_ZERO , PolyFillType .NON_ZERO );
195+ return checkIfIntersectionOccurs (Paths .makePolyTreeToPaths (polyTree ), rect1 , true );
196+ }
197+
198+ private boolean checkIfIntersectionOccurs (Paths paths , Point [] rect1 , boolean isDegenerate ) {
199+ if (paths .isEmpty ()) {
200+ return false ;
195201 }
202+ final LongRect intersectionRectangle = paths .getBounds ();
203+ // If the user defines a overlappingRatio we use this to calculate whether it intersects enough
204+ // To pass as an intersection
205+ if (properties .getOverlapRatio () == null ) {
206+ return !checkIfIntersectionRectangleDegenerate (intersectionRectangle , isDegenerate );
207+ }
208+ final double overlappedArea = CleanUpHelperUtil .calculatePolygonArea (rect1 );
209+ final double intersectionArea = ClipperBridge .longRectCalculateHeight (intersectionRectangle ) *
210+ ClipperBridge .longRectCalculateWidth (intersectionRectangle );
211+ final double percentageOfOverlapping = intersectionArea / overlappedArea ;
212+ final float SMALL_VALUE_FOR_ROUNDING_ERRORS = 1e-5f ;
213+ return percentageOfOverlapping + SMALL_VALUE_FOR_ROUNDING_ERRORS > properties .getOverlapRatio ();
196214 }
197215
198216 /**
@@ -274,7 +292,7 @@ FilteredImagesCache.FilteredImageKey createFilteredImageKey(PdfImageXObject imag
274292 * @return a filtered {@link com.itextpdf.kernel.geom.Path} object.
275293 */
276294 private com .itextpdf .kernel .geom .Path filterFillPath (com .itextpdf .kernel .geom .Path path ,
277- Matrix ctm , int fillingRule ) {
295+ Matrix ctm , int fillingRule ) {
278296 path .closeAllSubpaths ();
279297
280298 IClipper clipper = new DefaultClipper ();
@@ -336,8 +354,8 @@ private List<Rectangle> getImageAreasToBeCleaned(Matrix imageCtm) {
336354 }
337355
338356 private com .itextpdf .kernel .geom .Path filterStrokePath (com .itextpdf .kernel .geom .Path sourcePath , Matrix ctm ,
339- float lineWidth , int lineCapStyle , int lineJoinStyle ,
340- float miterLimit , LineDashPattern lineDashPattern ) {
357+ float lineWidth , int lineCapStyle , int lineJoinStyle ,
358+ float miterLimit , LineDashPattern lineDashPattern ) {
341359 com .itextpdf .kernel .geom .Path path = sourcePath ;
342360 JoinType joinType = ClipperBridge .getJoinType (lineJoinStyle );
343361 EndType endType = ClipperBridge .getEndType (lineCapStyle );
@@ -420,15 +438,14 @@ private static FilterResult<ImageData> filterImage(PdfImageXObject image, List<R
420438 * is true) and it is included into intersecting rectangle, this method returns false,
421439 * despite of the intersection rectangle is degenerate.
422440 *
423- * @param rect intersection rectangle
441+ * @param rect intersection rectangle
424442 * @param isIntersectSubjectDegenerate value, specifying if the intersection subject
425443 * is degenerate.
426444 * @return true - if the intersection rectangle is degenerate.
427445 */
428- private static boolean checkIfIntersectionRectangleDegenerate (LongRect rect ,
429- boolean isIntersectSubjectDegenerate ) {
430- float width = (float )(Math .abs (rect .left - rect .right ) / ClipperBridge .floatMultiplier );
431- float height = (float )(Math .abs (rect .top - rect .bottom ) / ClipperBridge .floatMultiplier );
446+ private static boolean checkIfIntersectionRectangleDegenerate (LongRect rect , boolean isIntersectSubjectDegenerate ) {
447+ final float width = ClipperBridge .longRectCalculateWidth (rect );
448+ final float height = ClipperBridge .longRectCalculateHeight (rect );
432449 return isIntersectSubjectDegenerate ? (width < EPS && height < EPS ) : (width < EPS || height < EPS );
433450 }
434451
@@ -466,7 +483,7 @@ private static boolean isSupportedFilterForDirectImageCleanup(PdfObject filter)
466483 return true ;
467484 }
468485 if (filter .isName ()) {
469- return !NOT_SUPPORTED_FILTERS_FOR_DIRECT_CLEANUP .contains ((PdfName )filter );
486+ return !NOT_SUPPORTED_FILTERS_FOR_DIRECT_CLEANUP .contains ((PdfName ) filter );
470487 } else if (filter .isArray ()) {
471488 PdfArray filterArray = (PdfArray ) filter ;
472489 for (int i = 0 ; i < filterArray .size (); ++i ) {
@@ -508,7 +525,7 @@ private static Rectangle transformRectIntoImageCoordinates(Rectangle rect, Matri
508525 * Filters image content using direct manipulation over PDF image samples stream. Implemented according to ISO 32000-2,
509526 * "8.9.3 Sample representation".
510527 *
511- * @param image image XObject which will be filtered
528+ * @param image image XObject which will be filtered
512529 * @param imageAreasToBeCleaned list of rectangle areas for clean up with coordinates in (0,1)x(0,1) space
513530 * @return raw bytes of the PDF image samples stream which is already cleaned.
514531 */
@@ -529,7 +546,7 @@ private static byte[] processImageDirectly(PdfImageXObject image, List<Rectangle
529546 throw new IllegalArgumentException ("/BitsPerComponent only allowed values are: 1, 2, 4, 8 and 16." );
530547 }
531548
532- double bytesInComponent = (double )bpc / 8 ;
549+ double bytesInComponent = (double ) bpc / 8 ;
533550 int firstComponentInByte = 0 ;
534551 if (bpc < 16 ) {
535552 for (int i = 0 ; i < bpc ; ++i ) {
@@ -544,7 +561,7 @@ private static byte[] processImageDirectly(PdfImageXObject image, List<Rectangle
544561 rowPadding = (int ) (8 - (width * bpc ) % 8 );
545562 }
546563 for (Rectangle rect : imageAreasToBeCleaned ) {
547- int [] cleanImgRect = CleanUpHelperUtil .getImageRectToClean (rect , (int )width , (int )height );
564+ int [] cleanImgRect = CleanUpHelperUtil .getImageRectToClean (rect , (int ) width , (int ) height );
548565 for (int j = cleanImgRect [Y ]; j < cleanImgRect [Y ] + cleanImgRect [H ]; ++j ) {
549566 for (int i = cleanImgRect [X ]; i < cleanImgRect [X ] + cleanImgRect [W ]; ++i ) {
550567 // based on assumption that numOfComponents always equals 1, because this method is only for monochrome and grayscale images
@@ -751,7 +768,6 @@ private static Point[] transformPoints(Matrix transformationMatrix, boolean inve
751768 private static Point [] getTextRectangle (TextRenderInfo renderInfo ) {
752769 LineSegment ascent = renderInfo .getAscentLine ();
753770 LineSegment descent = renderInfo .getDescentLine ();
754-
755771 return new Point []{
756772 new Point (ascent .getStartPoint ().get (0 ), ascent .getStartPoint ().get (1 )),
757773 new Point (ascent .getEndPoint ().get (0 ), ascent .getEndPoint ().get (1 )),
0 commit comments