Skip to content

Commit

Permalink
Make 'floatMultiplier' in ClipperBridge non-static configuration
Browse files Browse the repository at this point in the history
DEVSIX-5770
DEVSIX-1279
  • Loading branch information
AnhelinaM committed Oct 10, 2024
1 parent e84bb3e commit 1bb85b3
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ public void updateCtm(Matrix newCtm) {
/**
* Intersects the current clipping path with the given path.
*
* <p>
* <strong>Note:</strong> Coordinates of the given path should be in
* the transformed user space.
*
Expand All @@ -89,18 +90,20 @@ public void clip(Path path, int fillingRule) {
pathCopy.closeAllSubpaths();

IClipper clipper = new DefaultClipper();
ClipperBridge.addPath(clipper, clippingPath, IClipper.PolyType.SUBJECT);
ClipperBridge.addPath(clipper, pathCopy, IClipper.PolyType.CLIP);
ClipperBridge clipperBridge = new ClipperBridge(clippingPath, pathCopy);
clipperBridge.addPath(clipper, clippingPath, IClipper.PolyType.SUBJECT);
clipperBridge.addPath(clipper, pathCopy, IClipper.PolyType.CLIP);

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

clippingPath = ClipperBridge.convertToPath(resultTree);
clippingPath = clipperBridge.convertToPath(resultTree);
}

/**
* Getter for the current clipping path.
*
* <p>
* <strong>Note:</strong> The returned clipping path is in the transformed user space, so
* if you want to get it in default user space, apply transformation matrix ({@link CanvasGraphicsState#getCtm()}).
*
Expand All @@ -113,6 +116,7 @@ public Path getClippingPath() {
/**
* Sets the current clipping path to the specified path.
*
* <p>
* <strong>Note:</strong>This method doesn't modify existing clipping path,
* it simply replaces it with the new one instead.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ This file is part of the iText (R) project.
import java.util.List;

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

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

private ClipperBridge() {
//empty constructor
private double approximatedFloatMultiplier = Math.pow(10, 14);

/**
* Creates new {@link ClipperBridge} instance with default float multiplier value which is 10^14.
*
* <p>
* Since the clipper library uses integer coordinates, we should convert our floating point numbers into fixed
* point numbers by multiplying by float multiplier coefficient. It is possible to vary it to adjust the preciseness
* of the calculations: if static {@link #floatMultiplier} is specified, it will be used for all ClipperBridge
* instances and default value will be ignored.
*/
public ClipperBridge() {
// Empty constructor.
}

/**
* Creates new {@link ClipperBridge} instance with adjusted float multiplier value. This instance will work
* correctly with the provided paths only.
*
* <p>
* Since the clipper library uses integer coordinates, we should convert our floating point numbers into fixed
* point numbers by multiplying by float multiplier coefficient. It is calculated automatically, however
* it is possible to vary it to adjust the preciseness of the calculations: if static {@link #floatMultiplier} is
* specified, it will be used for all ClipperBridge instances and automatic calculation won't work.
*
* @param paths paths to calculate multiplier coefficient to convert floating point numbers into fixed point numbers
*/
public ClipperBridge(com.itextpdf.kernel.geom.Path... paths) {
if (floatMultiplier == null) {
List<com.itextpdf.kernel.geom.Point> pointsList = new ArrayList<>();
for (com.itextpdf.kernel.geom.Path path : paths) {
for (Subpath subpath : path.getSubpaths()) {
if (!subpath.isSinglePointClosed() && !subpath.isSinglePointOpen()) {
pointsList.addAll(subpath.getPiecewiseLinearApproximation());
}
}
}
calculateFloatMultiplier(pointsList.toArray(new com.itextpdf.kernel.geom.Point[0]));
}
}

/**
* Creates new {@link ClipperBridge} instance with adjusted float multiplier value. This instance will work
* correctly with the provided point only.
*
* <p>
* Since the clipper library uses integer coordinates, we should convert our floating point numbers into fixed
* point numbers by multiplying by float multiplier coefficient. It is calculated automatically, however
* it is possible to vary it to adjust the preciseness of the calculations: if static {@link #floatMultiplier} is
* specified, it will be used for all ClipperBridge instances and automatic calculation won't work.
*
* @param points points to calculate multiplier coefficient to convert floating point numbers
* into fixed point numbers
*/
public ClipperBridge(com.itextpdf.kernel.geom.Point[]... points) {
if (floatMultiplier == null) {
calculateFloatMultiplier(points);
}
}

/**
Expand All @@ -61,7 +123,7 @@ private ClipperBridge() {
* @param result {@link PolyTree} object to convert
* @return resultant {@link com.itextpdf.kernel.geom.Path} object
*/
public static com.itextpdf.kernel.geom.Path convertToPath(PolyTree result) {
public com.itextpdf.kernel.geom.Path convertToPath(PolyTree result) {
com.itextpdf.kernel.geom.Path path = new com.itextpdf.kernel.geom.Path();
PolyNode node = result.getFirst();

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

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

for (Point.LongPoint point : points) {
convertedPoints.add(new com.itextpdf.kernel.geom.Point(
point.getX() / floatMultiplier,
point.getY() / floatMultiplier
point.getX() / getFloatMultiplier(),
point.getY() / getFloatMultiplier()
));
}

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

for (com.itextpdf.kernel.geom.Point point : points) {
convertedPoints.add(new Point.LongPoint(
floatMultiplier * point.getX(),
floatMultiplier * point.getY()
getFloatMultiplier() * point.getX(),
getFloatMultiplier() * point.getY()
));
}

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

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

Expand All @@ -267,8 +331,8 @@ public static boolean addPolylineSubjectToClipper(IClipper clipper, com.itextpdf
*
* @return the width of the rectangle.
*/
public static float longRectCalculateWidth(LongRect rect) {
return (float) (Math.abs(rect.left - rect.right) / ClipperBridge.floatMultiplier);
public float longRectCalculateWidth(LongRect rect) {
return (float) (Math.abs(rect.left - rect.right) / getFloatMultiplier());
}

/**
Expand All @@ -277,11 +341,23 @@ public static float longRectCalculateWidth(LongRect rect) {
*
* @return the height of the rectangle.
*/
public static float longRectCalculateHeight(LongRect rect) {
return (float) (Math.abs(rect.top - rect.bottom) / ClipperBridge.floatMultiplier);
public float longRectCalculateHeight(LongRect rect) {
return (float) (Math.abs(rect.top - rect.bottom) / getFloatMultiplier());
}

/**
* Gets multiplier coefficient for converting our floating point numbers into fixed point numbers.
*
* @return multiplier coefficient for converting our floating point numbers into fixed point numbers
*/
public double getFloatMultiplier() {
if (floatMultiplier == null) {
return approximatedFloatMultiplier;
}
return (double) floatMultiplier;
}

static void addContour(com.itextpdf.kernel.geom.Path path, List<Point.LongPoint> contour, boolean close) {
void addContour(com.itextpdf.kernel.geom.Path path, List<Point.LongPoint> contour, boolean close) {
List<com.itextpdf.kernel.geom.Point> floatContour = convertToFloatPoints(contour);
com.itextpdf.kernel.geom.Point point = floatContour.get(0);
path.moveTo((float) point.getX(), (float) point.getY());
Expand All @@ -295,4 +371,19 @@ static void addContour(com.itextpdf.kernel.geom.Path path, List<Point.LongPoint>
path.closeSubpath();
}
}

private void calculateFloatMultiplier(com.itextpdf.kernel.geom.Point[]... points) {
double maxPoint = 0;
for (com.itextpdf.kernel.geom.Point[] pointsArray : points) {
for (com.itextpdf.kernel.geom.Point point : pointsArray) {
maxPoint = Math.max(maxPoint, Math.abs(point.getX()));
maxPoint = Math.max(maxPoint, Math.abs(point.getY()));
}
}
// The significand of the double type is approximately 15 to 17 decimal digits for most platforms.
double epsilon = 1E-16;
if (maxPoint > epsilon) {
this.approximatedFloatMultiplier = Math.floor(MAX_ALLOWED_VALUE / maxPoint);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,36 +24,52 @@ This file is part of the iText (R) project.

import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.canvas.parser.clipper.ClipperBridge;
import com.itextpdf.kernel.pdf.canvas.parser.clipper.ClipperException;
import com.itextpdf.kernel.pdf.canvas.parser.clipper.ClipperExceptionConstant;
import com.itextpdf.kernel.pdf.canvas.parser.listener.LocationTextExtractionStrategy;
import com.itextpdf.test.AssertUtil;
import com.itextpdf.test.ExtendedITextTest;

import java.io.IOException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

import java.io.IOException;

@Tag("IntegrationTest")
public class PdfContentExtractionTest extends ExtendedITextTest {

private static final String sourceFolder = "./src/test/resources/com/itextpdf/kernel/parser/PdfContentExtractionTest/";

private static final String SOURCE_FOLDER =
"./src/test/resources/com/itextpdf/kernel/parser/PdfContentExtractionTest/";

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

PdfDocument pdfDocument = new PdfDocument(new PdfReader(inputFileName));
PdfDocumentContentParser contentParser = new PdfDocumentContentParser(pdfDocument);

Exception e = Assertions.assertThrows(ClipperException.class,
() -> contentParser.processContent(1, new LocationTextExtractionStrategy())
);
Assertions.assertEquals(ClipperExceptionConstant.COORDINATE_OUTSIDE_ALLOWED_RANGE, e.getMessage());
@Test
public void contentExtractionInDocWithStaticFloatMultiplierTest() throws IOException {
String inputFileName = SOURCE_FOLDER + "docWithBigCoordinates.pdf";
// In this document the CTM shrinks coordinates and these coordinates are large numbers.
// At the moment creation of this test clipper has a problem with handling large numbers
// since internally it deals with integers and has to multiply large numbers even more
// for internal purposes
try (PdfDocument pdfDocument = new PdfDocument(new PdfReader(inputFileName))) {
PdfDocumentContentParser contentParser = new PdfDocumentContentParser(pdfDocument);
ClipperBridge.floatMultiplier = Math.pow(10, 14);
Exception e = Assertions.assertThrows(ClipperException.class,
() -> contentParser.processContent(1, new LocationTextExtractionStrategy())
);
Assertions.assertEquals(ClipperExceptionConstant.COORDINATE_OUTSIDE_ALLOWED_RANGE, e.getMessage());
ClipperBridge.floatMultiplier = null;
}
}
}
Loading

0 comments on commit 1bb85b3

Please sign in to comment.