@@ -95,10 +95,13 @@ class PdfCleanUpFilter {
95
95
private static final Set <PdfName > NOT_SUPPORTED_FILTERS_FOR_DIRECT_CLEANUP = Collections .unmodifiableSet (
96
96
new LinkedHashSet <>(Arrays .asList (PdfName .JBIG2Decode , PdfName .DCTDecode , PdfName .JPXDecode )));
97
97
98
- private List <Rectangle > regions ;
98
+ private final List <Rectangle > regions ;
99
99
100
- public PdfCleanUpFilter (List <Rectangle > regions ) {
100
+ private final CleanUpProperties properties ;
101
+
102
+ public PdfCleanUpFilter (List <Rectangle > regions , CleanUpProperties properties ) {
101
103
this .regions = regions ;
104
+ this .properties = properties ;
102
105
}
103
106
104
107
static boolean imageSupportsDirectCleanup (PdfImageXObject image ) {
@@ -118,7 +121,7 @@ static boolean imageSupportsDirectCleanup(PdfImageXObject image) {
118
121
* are never considered as intersecting.
119
122
* @return true if the rectangles intersect, false otherwise
120
123
*/
121
- static boolean checkIfRectanglesIntersect (Point [] rect1 , Point [] rect2 ) {
124
+ boolean checkIfRectanglesIntersect (Point [] rect1 , Point [] rect2 ) {
122
125
IClipper clipper = new DefaultClipper ();
123
126
// If the redaction area is degenerate, the result will be false
124
127
if (!ClipperBridge .addPolygonToClipper (clipper , rect2 , PolyType .CLIP )) {
@@ -170,29 +173,44 @@ static boolean checkIfRectanglesIntersect(Point[] rect1, Point[] rect2) {
170
173
// working with paths is considered to be a bit faster in terms of performance.
171
174
Paths paths = new Paths ();
172
175
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
+
177
190
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 ;
195
201
}
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 ();
196
214
}
197
215
198
216
/**
@@ -274,7 +292,7 @@ FilteredImagesCache.FilteredImageKey createFilteredImageKey(PdfImageXObject imag
274
292
* @return a filtered {@link com.itextpdf.kernel.geom.Path} object.
275
293
*/
276
294
private com .itextpdf .kernel .geom .Path filterFillPath (com .itextpdf .kernel .geom .Path path ,
277
- Matrix ctm , int fillingRule ) {
295
+ Matrix ctm , int fillingRule ) {
278
296
path .closeAllSubpaths ();
279
297
280
298
IClipper clipper = new DefaultClipper ();
@@ -336,8 +354,8 @@ private List<Rectangle> getImageAreasToBeCleaned(Matrix imageCtm) {
336
354
}
337
355
338
356
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 ) {
341
359
com .itextpdf .kernel .geom .Path path = sourcePath ;
342
360
JoinType joinType = ClipperBridge .getJoinType (lineJoinStyle );
343
361
EndType endType = ClipperBridge .getEndType (lineCapStyle );
@@ -420,15 +438,14 @@ private static FilterResult<ImageData> filterImage(PdfImageXObject image, List<R
420
438
* is true) and it is included into intersecting rectangle, this method returns false,
421
439
* despite of the intersection rectangle is degenerate.
422
440
*
423
- * @param rect intersection rectangle
441
+ * @param rect intersection rectangle
424
442
* @param isIntersectSubjectDegenerate value, specifying if the intersection subject
425
443
* is degenerate.
426
444
* @return true - if the intersection rectangle is degenerate.
427
445
*/
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 );
432
449
return isIntersectSubjectDegenerate ? (width < EPS && height < EPS ) : (width < EPS || height < EPS );
433
450
}
434
451
@@ -466,7 +483,7 @@ private static boolean isSupportedFilterForDirectImageCleanup(PdfObject filter)
466
483
return true ;
467
484
}
468
485
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 );
470
487
} else if (filter .isArray ()) {
471
488
PdfArray filterArray = (PdfArray ) filter ;
472
489
for (int i = 0 ; i < filterArray .size (); ++i ) {
@@ -508,7 +525,7 @@ private static Rectangle transformRectIntoImageCoordinates(Rectangle rect, Matri
508
525
* Filters image content using direct manipulation over PDF image samples stream. Implemented according to ISO 32000-2,
509
526
* "8.9.3 Sample representation".
510
527
*
511
- * @param image image XObject which will be filtered
528
+ * @param image image XObject which will be filtered
512
529
* @param imageAreasToBeCleaned list of rectangle areas for clean up with coordinates in (0,1)x(0,1) space
513
530
* @return raw bytes of the PDF image samples stream which is already cleaned.
514
531
*/
@@ -529,7 +546,7 @@ private static byte[] processImageDirectly(PdfImageXObject image, List<Rectangle
529
546
throw new IllegalArgumentException ("/BitsPerComponent only allowed values are: 1, 2, 4, 8 and 16." );
530
547
}
531
548
532
- double bytesInComponent = (double )bpc / 8 ;
549
+ double bytesInComponent = (double ) bpc / 8 ;
533
550
int firstComponentInByte = 0 ;
534
551
if (bpc < 16 ) {
535
552
for (int i = 0 ; i < bpc ; ++i ) {
@@ -544,7 +561,7 @@ private static byte[] processImageDirectly(PdfImageXObject image, List<Rectangle
544
561
rowPadding = (int ) (8 - (width * bpc ) % 8 );
545
562
}
546
563
for (Rectangle rect : imageAreasToBeCleaned ) {
547
- int [] cleanImgRect = CleanUpHelperUtil .getImageRectToClean (rect , (int )width , (int )height );
564
+ int [] cleanImgRect = CleanUpHelperUtil .getImageRectToClean (rect , (int ) width , (int ) height );
548
565
for (int j = cleanImgRect [Y ]; j < cleanImgRect [Y ] + cleanImgRect [H ]; ++j ) {
549
566
for (int i = cleanImgRect [X ]; i < cleanImgRect [X ] + cleanImgRect [W ]; ++i ) {
550
567
// 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
751
768
private static Point [] getTextRectangle (TextRenderInfo renderInfo ) {
752
769
LineSegment ascent = renderInfo .getAscentLine ();
753
770
LineSegment descent = renderInfo .getDescentLine ();
754
-
755
771
return new Point []{
756
772
new Point (ascent .getStartPoint ().get (0 ), ascent .getStartPoint ().get (1 )),
757
773
new Point (ascent .getEndPoint ().get (0 ), ascent .getEndPoint ().get (1 )),
0 commit comments