@@ -30,8 +30,9 @@ This file is part of the iText (R) project.
3030import 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 */
4344public 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}
0 commit comments