Skip to content

Commit 1bb85b3

Browse files
committed
Make 'floatMultiplier' in ClipperBridge non-static configuration
DEVSIX-5770 DEVSIX-1279
1 parent e84bb3e commit 1bb85b3

File tree

4 files changed

+185
-45
lines changed

4 files changed

+185
-45
lines changed

kernel/src/main/java/com/itextpdf/kernel/pdf/canvas/parser/ParserGraphicsState.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ public void updateCtm(Matrix newCtm) {
7272
/**
7373
* Intersects the current clipping path with the given path.
7474
*
75+
* <p>
7576
* <strong>Note:</strong> Coordinates of the given path should be in
7677
* the transformed user space.
7778
*
@@ -89,18 +90,20 @@ public void clip(Path path, int fillingRule) {
8990
pathCopy.closeAllSubpaths();
9091

9192
IClipper clipper = new DefaultClipper();
92-
ClipperBridge.addPath(clipper, clippingPath, IClipper.PolyType.SUBJECT);
93-
ClipperBridge.addPath(clipper, pathCopy, IClipper.PolyType.CLIP);
93+
ClipperBridge clipperBridge = new ClipperBridge(clippingPath, pathCopy);
94+
clipperBridge.addPath(clipper, clippingPath, IClipper.PolyType.SUBJECT);
95+
clipperBridge.addPath(clipper, pathCopy, IClipper.PolyType.CLIP);
9496

9597
PolyTree resultTree = new PolyTree();
9698
clipper.execute(IClipper.ClipType.INTERSECTION, resultTree, IClipper.PolyFillType.NON_ZERO, ClipperBridge.getFillType(fillingRule));
9799

98-
clippingPath = ClipperBridge.convertToPath(resultTree);
100+
clippingPath = clipperBridge.convertToPath(resultTree);
99101
}
100102

101103
/**
102104
* Getter for the current clipping path.
103105
*
106+
* <p>
104107
* <strong>Note:</strong> The returned clipping path is in the transformed user space, so
105108
* if you want to get it in default user space, apply transformation matrix ({@link CanvasGraphicsState#getCtm()}).
106109
*
@@ -113,6 +116,7 @@ public Path getClippingPath() {
113116
/**
114117
* Sets the current clipping path to the specified path.
115118
*
119+
* <p>
116120
* <strong>Note:</strong>This method doesn't modify existing clipping path,
117121
* it simply replaces it with the new one instead.
118122
*

kernel/src/main/java/com/itextpdf/kernel/pdf/canvas/parser/clipper/ClipperBridge.java

Lines changed: 113 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ This file is part of the iText (R) project.
3030
import java.util.List;
3131

3232
/**
33-
* This class contains variety of methods allowing to convert iText
34-
* abstractions into the abstractions of the Clipper library and vise versa.
33+
* This class contains a variety of methods allowing the conversion of iText
34+
* abstractions into abstractions of the Clipper library, and vice versa.
35+
*
3536
* <p>
3637
* For example:
3738
* <ul>
@@ -41,17 +42,78 @@ This file is part of the iText (R) project.
4142
* </ul>
4243
*/
4344
public final class ClipperBridge {
45+
private static final long MAX_ALLOWED_VALUE = 0x3FFFFFFFFFFFFFL;
4446

4547
/**
4648
* Since the clipper library uses integer coordinates, we should convert
4749
* our floating point numbers into fixed point numbers by multiplying by
4850
* this coefficient. Vary it to adjust the preciseness of the calculations.
51+
*
52+
* <p>
53+
* Note that if this value is specified, it will be used for all ClipperBridge instances and
54+
* dynamic float multiplier calculation will be disabled.
55+
*
4956
*/
50-
//TODO DEVSIX-5770 make this constant a single non-static configuration
51-
public static double floatMultiplier = Math.pow(10, 14);
57+
public static Double floatMultiplier;
5258

53-
private ClipperBridge() {
54-
//empty constructor
59+
private double approximatedFloatMultiplier = Math.pow(10, 14);
60+
61+
/**
62+
* Creates new {@link ClipperBridge} instance with default float multiplier value which is 10^14.
63+
*
64+
* <p>
65+
* Since the clipper library uses integer coordinates, we should convert our floating point numbers into fixed
66+
* point numbers by multiplying by float multiplier coefficient. It is possible to vary it to adjust the preciseness
67+
* of the calculations: if static {@link #floatMultiplier} is specified, it will be used for all ClipperBridge
68+
* instances and default value will be ignored.
69+
*/
70+
public ClipperBridge() {
71+
// Empty constructor.
72+
}
73+
74+
/**
75+
* Creates new {@link ClipperBridge} instance with adjusted float multiplier value. This instance will work
76+
* correctly with the provided paths only.
77+
*
78+
* <p>
79+
* Since the clipper library uses integer coordinates, we should convert our floating point numbers into fixed
80+
* point numbers by multiplying by float multiplier coefficient. It is calculated automatically, however
81+
* it is possible to vary it to adjust the preciseness of the calculations: if static {@link #floatMultiplier} is
82+
* specified, it will be used for all ClipperBridge instances and automatic calculation won't work.
83+
*
84+
* @param paths paths to calculate multiplier coefficient to convert floating point numbers into fixed point numbers
85+
*/
86+
public ClipperBridge(com.itextpdf.kernel.geom.Path... paths) {
87+
if (floatMultiplier == null) {
88+
List<com.itextpdf.kernel.geom.Point> pointsList = new ArrayList<>();
89+
for (com.itextpdf.kernel.geom.Path path : paths) {
90+
for (Subpath subpath : path.getSubpaths()) {
91+
if (!subpath.isSinglePointClosed() && !subpath.isSinglePointOpen()) {
92+
pointsList.addAll(subpath.getPiecewiseLinearApproximation());
93+
}
94+
}
95+
}
96+
calculateFloatMultiplier(pointsList.toArray(new com.itextpdf.kernel.geom.Point[0]));
97+
}
98+
}
99+
100+
/**
101+
* Creates new {@link ClipperBridge} instance with adjusted float multiplier value. This instance will work
102+
* correctly with the provided point only.
103+
*
104+
* <p>
105+
* Since the clipper library uses integer coordinates, we should convert our floating point numbers into fixed
106+
* point numbers by multiplying by float multiplier coefficient. It is calculated automatically, however
107+
* it is possible to vary it to adjust the preciseness of the calculations: if static {@link #floatMultiplier} is
108+
* specified, it will be used for all ClipperBridge instances and automatic calculation won't work.
109+
*
110+
* @param points points to calculate multiplier coefficient to convert floating point numbers
111+
* into fixed point numbers
112+
*/
113+
public ClipperBridge(com.itextpdf.kernel.geom.Point[]... points) {
114+
if (floatMultiplier == null) {
115+
calculateFloatMultiplier(points);
116+
}
55117
}
56118

57119
/**
@@ -61,7 +123,7 @@ private ClipperBridge() {
61123
* @param result {@link PolyTree} object to convert
62124
* @return resultant {@link com.itextpdf.kernel.geom.Path} object
63125
*/
64-
public static com.itextpdf.kernel.geom.Path convertToPath(PolyTree result) {
126+
public com.itextpdf.kernel.geom.Path convertToPath(PolyTree result) {
65127
com.itextpdf.kernel.geom.Path path = new com.itextpdf.kernel.geom.Path();
66128
PolyNode node = result.getFirst();
67129

@@ -79,7 +141,7 @@ public static com.itextpdf.kernel.geom.Path convertToPath(PolyTree result) {
79141
* @param path The {@link com.itextpdf.kernel.geom.Path} object to be added to the {@link IClipper}.
80142
* @param polyType See {@link IClipper.PolyType}.
81143
*/
82-
public static void addPath(IClipper clipper, com.itextpdf.kernel.geom.Path path, IClipper.PolyType polyType) {
144+
public void addPath(IClipper clipper, com.itextpdf.kernel.geom.Path path, IClipper.PolyType polyType) {
83145
for (Subpath subpath : path.getSubpaths()) {
84146
if (!subpath.isSinglePointClosed() && !subpath.isSinglePointOpen()) {
85147
List<com.itextpdf.kernel.geom.Point> linearApproxPoints = subpath.getPiecewiseLinearApproximation();
@@ -101,7 +163,8 @@ public static void addPath(IClipper clipper, com.itextpdf.kernel.geom.Path path,
101163
* {@link IClipper.EndType#OPEN_ROUND}
102164
* @return {@link java.util.List} consisting of all degenerate iText {@link Subpath}s of the path.
103165
*/
104-
public static List<Subpath> addPath(ClipperOffset offset, com.itextpdf.kernel.geom.Path path, IClipper.JoinType joinType, IClipper.EndType endType) {
166+
public List<Subpath> addPath(ClipperOffset offset, com.itextpdf.kernel.geom.Path path, IClipper.JoinType joinType,
167+
IClipper.EndType endType) {
105168
List<Subpath> degenerateSubpaths = new ArrayList<>();
106169

107170
for (Subpath subpath : path.getSubpaths()) {
@@ -135,13 +198,13 @@ public static List<Subpath> addPath(ClipperOffset offset, com.itextpdf.kernel.ge
135198
* @param points the list of {@link Point.LongPoint} objects to convert
136199
* @return the resultant list of {@link com.itextpdf.kernel.geom.Point} objects.
137200
*/
138-
public static List<com.itextpdf.kernel.geom.Point> convertToFloatPoints(List<Point.LongPoint> points) {
201+
public List<com.itextpdf.kernel.geom.Point> convertToFloatPoints(List<Point.LongPoint> points) {
139202
List<com.itextpdf.kernel.geom.Point> convertedPoints = new ArrayList<>(points.size());
140203

141204
for (Point.LongPoint point : points) {
142205
convertedPoints.add(new com.itextpdf.kernel.geom.Point(
143-
point.getX() / floatMultiplier,
144-
point.getY() / floatMultiplier
206+
point.getX() / getFloatMultiplier(),
207+
point.getY() / getFloatMultiplier()
145208
));
146209
}
147210

@@ -155,13 +218,13 @@ public static List<com.itextpdf.kernel.geom.Point> convertToFloatPoints(List<Poi
155218
* @param points the list of {@link com.itextpdf.kernel.geom.Point} objects to convert
156219
* @return the resultant list of {@link Point.LongPoint} objects.
157220
*/
158-
public static List<Point.LongPoint> convertToLongPoints(List<com.itextpdf.kernel.geom.Point> points) {
221+
public List<Point.LongPoint> convertToLongPoints(List<com.itextpdf.kernel.geom.Point> points) {
159222
List<Point.LongPoint> convertedPoints = new ArrayList<>(points.size());
160223

161224
for (com.itextpdf.kernel.geom.Point point : points) {
162225
convertedPoints.add(new Point.LongPoint(
163-
floatMultiplier * point.getX(),
164-
floatMultiplier * point.getY()
226+
getFloatMultiplier() * point.getX(),
227+
getFloatMultiplier() * point.getY()
165228
));
166229
}
167230

@@ -238,7 +301,8 @@ public static IClipper.PolyFillType getFillType(int fillingRule) {
238301
* path is a subject of clipping or a part of the clipping polygon.
239302
* @return true if polygon path was successfully added, false otherwise.
240303
*/
241-
public static boolean addPolygonToClipper(IClipper clipper, com.itextpdf.kernel.geom.Point[] polyVertices, IClipper.PolyType polyType) {
304+
public boolean addPolygonToClipper(IClipper clipper, com.itextpdf.kernel.geom.Point[] polyVertices,
305+
IClipper.PolyType polyType) {
242306
return clipper.addPath(new Path(convertToLongPoints(new ArrayList<>(Arrays.asList(polyVertices)))), polyType, true);
243307
}
244308

@@ -257,7 +321,7 @@ public static boolean addPolygonToClipper(IClipper clipper, com.itextpdf.kernel.
257321
* to clipper path and added to the clipper instance.
258322
* @return true if polyline path was successfully added, false otherwise.
259323
*/
260-
public static boolean addPolylineSubjectToClipper(IClipper clipper, com.itextpdf.kernel.geom.Point[] lineVertices) {
324+
public boolean addPolylineSubjectToClipper(IClipper clipper, com.itextpdf.kernel.geom.Point[] lineVertices) {
261325
return clipper.addPath(new Path(convertToLongPoints(new ArrayList<>(Arrays.asList(lineVertices)))), IClipper.PolyType.SUBJECT, false);
262326
}
263327

@@ -267,8 +331,8 @@ public static boolean addPolylineSubjectToClipper(IClipper clipper, com.itextpdf
267331
*
268332
* @return the width of the rectangle.
269333
*/
270-
public static float longRectCalculateWidth(LongRect rect) {
271-
return (float) (Math.abs(rect.left - rect.right) / ClipperBridge.floatMultiplier);
334+
public float longRectCalculateWidth(LongRect rect) {
335+
return (float) (Math.abs(rect.left - rect.right) / getFloatMultiplier());
272336
}
273337

274338
/**
@@ -277,11 +341,23 @@ public static float longRectCalculateWidth(LongRect rect) {
277341
*
278342
* @return the height of the rectangle.
279343
*/
280-
public static float longRectCalculateHeight(LongRect rect) {
281-
return (float) (Math.abs(rect.top - rect.bottom) / ClipperBridge.floatMultiplier);
344+
public float longRectCalculateHeight(LongRect rect) {
345+
return (float) (Math.abs(rect.top - rect.bottom) / getFloatMultiplier());
346+
}
347+
348+
/**
349+
* Gets multiplier coefficient for converting our floating point numbers into fixed point numbers.
350+
*
351+
* @return multiplier coefficient for converting our floating point numbers into fixed point numbers
352+
*/
353+
public double getFloatMultiplier() {
354+
if (floatMultiplier == null) {
355+
return approximatedFloatMultiplier;
356+
}
357+
return (double) floatMultiplier;
282358
}
283359

284-
static void addContour(com.itextpdf.kernel.geom.Path path, List<Point.LongPoint> contour, boolean close) {
360+
void addContour(com.itextpdf.kernel.geom.Path path, List<Point.LongPoint> contour, boolean close) {
285361
List<com.itextpdf.kernel.geom.Point> floatContour = convertToFloatPoints(contour);
286362
com.itextpdf.kernel.geom.Point point = floatContour.get(0);
287363
path.moveTo((float) point.getX(), (float) point.getY());
@@ -295,4 +371,19 @@ static void addContour(com.itextpdf.kernel.geom.Path path, List<Point.LongPoint>
295371
path.closeSubpath();
296372
}
297373
}
374+
375+
private void calculateFloatMultiplier(com.itextpdf.kernel.geom.Point[]... points) {
376+
double maxPoint = 0;
377+
for (com.itextpdf.kernel.geom.Point[] pointsArray : points) {
378+
for (com.itextpdf.kernel.geom.Point point : pointsArray) {
379+
maxPoint = Math.max(maxPoint, Math.abs(point.getX()));
380+
maxPoint = Math.max(maxPoint, Math.abs(point.getY()));
381+
}
382+
}
383+
// The significand of the double type is approximately 15 to 17 decimal digits for most platforms.
384+
double epsilon = 1E-16;
385+
if (maxPoint > epsilon) {
386+
this.approximatedFloatMultiplier = Math.floor(MAX_ALLOWED_VALUE / maxPoint);
387+
}
388+
}
298389
}

kernel/src/test/java/com/itextpdf/kernel/pdf/canvas/parser/PdfContentExtractionTest.java

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,36 +24,52 @@ This file is part of the iText (R) project.
2424

2525
import com.itextpdf.kernel.pdf.PdfDocument;
2626
import com.itextpdf.kernel.pdf.PdfReader;
27+
import com.itextpdf.kernel.pdf.canvas.parser.clipper.ClipperBridge;
2728
import com.itextpdf.kernel.pdf.canvas.parser.clipper.ClipperException;
2829
import com.itextpdf.kernel.pdf.canvas.parser.clipper.ClipperExceptionConstant;
2930
import com.itextpdf.kernel.pdf.canvas.parser.listener.LocationTextExtractionStrategy;
31+
import com.itextpdf.test.AssertUtil;
3032
import com.itextpdf.test.ExtendedITextTest;
31-
32-
import java.io.IOException;
3333
import org.junit.jupiter.api.Assertions;
34-
import org.junit.jupiter.api.Test;
3534
import org.junit.jupiter.api.Tag;
35+
import org.junit.jupiter.api.Test;
36+
37+
import java.io.IOException;
3638

3739
@Tag("IntegrationTest")
3840
public class PdfContentExtractionTest extends ExtendedITextTest {
39-
40-
private static final String sourceFolder = "./src/test/resources/com/itextpdf/kernel/parser/PdfContentExtractionTest/";
41+
42+
private static final String SOURCE_FOLDER =
43+
"./src/test/resources/com/itextpdf/kernel/parser/PdfContentExtractionTest/";
4144

4245
@Test
43-
//TODO: remove the expected exception construct once the issue is fixed (DEVSIX-1279)
4446
public void contentExtractionInDocWithBigCoordinatesTest() throws IOException {
45-
String inputFileName = sourceFolder + "docWithBigCoordinates.pdf";
46-
//In this document the CTM shrinks coordinates and this coordinates are large numbers.
47+
String inputFileName = SOURCE_FOLDER + "docWithBigCoordinates.pdf";
48+
// In this document the CTM shrinks coordinates and these coordinates are large numbers.
4749
// At the moment creation of this test clipper has a problem with handling large numbers
4850
// since internally it deals with integers and has to multiply large numbers even more
4951
// for internal purposes
52+
try (PdfDocument pdfDocument = new PdfDocument(new PdfReader(inputFileName))) {
53+
PdfDocumentContentParser contentParser = new PdfDocumentContentParser(pdfDocument);
54+
AssertUtil.doesNotThrow(() -> contentParser.processContent(1, new LocationTextExtractionStrategy()));
55+
}
56+
}
5057

51-
PdfDocument pdfDocument = new PdfDocument(new PdfReader(inputFileName));
52-
PdfDocumentContentParser contentParser = new PdfDocumentContentParser(pdfDocument);
53-
54-
Exception e = Assertions.assertThrows(ClipperException.class,
55-
() -> contentParser.processContent(1, new LocationTextExtractionStrategy())
56-
);
57-
Assertions.assertEquals(ClipperExceptionConstant.COORDINATE_OUTSIDE_ALLOWED_RANGE, e.getMessage());
58+
@Test
59+
public void contentExtractionInDocWithStaticFloatMultiplierTest() throws IOException {
60+
String inputFileName = SOURCE_FOLDER + "docWithBigCoordinates.pdf";
61+
// In this document the CTM shrinks coordinates and these coordinates are large numbers.
62+
// At the moment creation of this test clipper has a problem with handling large numbers
63+
// since internally it deals with integers and has to multiply large numbers even more
64+
// for internal purposes
65+
try (PdfDocument pdfDocument = new PdfDocument(new PdfReader(inputFileName))) {
66+
PdfDocumentContentParser contentParser = new PdfDocumentContentParser(pdfDocument);
67+
ClipperBridge.floatMultiplier = Math.pow(10, 14);
68+
Exception e = Assertions.assertThrows(ClipperException.class,
69+
() -> contentParser.processContent(1, new LocationTextExtractionStrategy())
70+
);
71+
Assertions.assertEquals(ClipperExceptionConstant.COORDINATE_OUTSIDE_ALLOWED_RANGE, e.getMessage());
72+
ClipperBridge.floatMultiplier = null;
73+
}
5874
}
5975
}

0 commit comments

Comments
 (0)