From 52792d54f48fe97000e72877e7e75b0665cdf064 Mon Sep 17 00:00:00 2001 From: Angelina Pavlovets Date: Fri, 11 Oct 2024 07:27:53 +0000 Subject: [PATCH] Make 'floatMultiplier' in ClipperBridge non-static configuration DEVSIX-5770 DEVSIX-1279 Autoported commit. Original commit hash: [1bb85b3f2] --- .../canvas/parser/PdfContentExtractionTest.cs | 34 +++- .../parser/clipperlib/ClipperBridgeTest.cs | 32 +++- .../pdf/canvas/parser/ParserGraphicsState.cs | 10 +- .../canvas/parser/clipperlib/ClipperBridge.cs | 157 +++++++++++++++--- port-hash | 2 +- 5 files changed, 192 insertions(+), 43 deletions(-) diff --git a/itext.tests/itext.kernel.tests/itext/kernel/pdf/canvas/parser/PdfContentExtractionTest.cs b/itext.tests/itext.kernel.tests/itext/kernel/pdf/canvas/parser/PdfContentExtractionTest.cs index 2cbb139509..40f98d54f7 100644 --- a/itext.tests/itext.kernel.tests/itext/kernel/pdf/canvas/parser/PdfContentExtractionTest.cs +++ b/itext.tests/itext.kernel.tests/itext/kernel/pdf/canvas/parser/PdfContentExtractionTest.cs @@ -29,22 +29,38 @@ You should have received a copy of the GNU Affero General Public License namespace iText.Kernel.Pdf.Canvas.Parser { [NUnit.Framework.Category("IntegrationTest")] public class PdfContentExtractionTest : ExtendedITextTest { - private static readonly String sourceFolder = iText.Test.TestUtil.GetParentProjectDirectory(NUnit.Framework.TestContext + private static readonly String SOURCE_FOLDER = iText.Test.TestUtil.GetParentProjectDirectory(NUnit.Framework.TestContext .CurrentContext.TestDirectory) + "/resources/itext/kernel/parser/PdfContentExtractionTest/"; [NUnit.Framework.Test] public virtual void ContentExtractionInDocWithBigCoordinatesTest() { - //TODO: remove the expected exception construct once the issue is fixed (DEVSIX-1279) - 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 - PdfDocument pdfDocument = new PdfDocument(new PdfReader(inputFileName)); - PdfDocumentContentParser contentParser = new PdfDocumentContentParser(pdfDocument); - Exception e = NUnit.Framework.Assert.Catch(typeof(ClipperException), () => contentParser.ProcessContent(1, - new LocationTextExtractionStrategy())); - NUnit.Framework.Assert.AreEqual(ClipperExceptionConstant.COORDINATE_OUTSIDE_ALLOWED_RANGE, e.Message); + using (PdfDocument pdfDocument = new PdfDocument(new PdfReader(inputFileName))) { + PdfDocumentContentParser contentParser = new PdfDocumentContentParser(pdfDocument); + NUnit.Framework.Assert.DoesNotThrow(() => contentParser.ProcessContent(1, new LocationTextExtractionStrategy + ())); + } + } + + [NUnit.Framework.Test] + public virtual void ContentExtractionInDocWithStaticFloatMultiplierTest() { + 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 + using (PdfDocument pdfDocument = new PdfDocument(new PdfReader(inputFileName))) { + PdfDocumentContentParser contentParser = new PdfDocumentContentParser(pdfDocument); + ClipperBridge.floatMultiplier = Math.Pow(10, 14); + Exception e = NUnit.Framework.Assert.Catch(typeof(ClipperException), () => contentParser.ProcessContent(1, + new LocationTextExtractionStrategy())); + NUnit.Framework.Assert.AreEqual(ClipperExceptionConstant.COORDINATE_OUTSIDE_ALLOWED_RANGE, e.Message); + ClipperBridge.floatMultiplier = null; + } } } } diff --git a/itext.tests/itext.kernel.tests/itext/kernel/pdf/canvas/parser/clipperlib/ClipperBridgeTest.cs b/itext.tests/itext.kernel.tests/itext/kernel/pdf/canvas/parser/clipperlib/ClipperBridgeTest.cs index 1791781011..7659abeca4 100644 --- a/itext.tests/itext.kernel.tests/itext/kernel/pdf/canvas/parser/clipperlib/ClipperBridgeTest.cs +++ b/itext.tests/itext.kernel.tests/itext/kernel/pdf/canvas/parser/clipperlib/ClipperBridgeTest.cs @@ -22,6 +22,7 @@ You should have received a copy of the GNU Affero General Public License */ using System.Collections.Generic; using System.Linq; +using iText.Commons.Utils; using iText.Kernel.Geom; using iText.Kernel.Pdf.Canvas; using iText.Test; @@ -48,11 +49,12 @@ public virtual void SquareClippingTest() { Path rectanglePath = new Path(); rectanglePath.AddSubpath(rectangleSubpath); Clipper clipper = new Clipper(); - ClipperBridge.AddPath(clipper, squarePath, PolyType.SUBJECT); - ClipperBridge.AddPath(clipper, rectanglePath, PolyType.CLIP); + ClipperBridge clipperBridge = new ClipperBridge(squarePath, rectanglePath); + clipperBridge.AddPath(clipper, squarePath, PolyType.SUBJECT); + clipperBridge.AddPath(clipper, rectanglePath, PolyType.CLIP); PolyTree polyTree = new PolyTree(); clipper.Execute(ClipType.UNION, polyTree); - Path result = ClipperBridge.ConvertToPath(polyTree); + Path result = clipperBridge.ConvertToPath(polyTree); NUnit.Framework.Assert.AreEqual(new Point(20, 40), result.GetCurrentPoint()); NUnit.Framework.Assert.AreEqual(2, result.GetSubpaths().Count); Subpath closedPath = result.GetSubpaths()[0]; @@ -95,14 +97,34 @@ public virtual void GetEndTypeTest() { public virtual void LongRectWidthTest() { IntRect longRect = new IntRect(14900000000000000L, 21275000000000000L, 71065802001953128L, 71075000000000000L ); - NUnit.Framework.Assert.AreEqual(561.658, ClipperBridge.LongRectCalculateWidth(longRect), 0.001f); + NUnit.Framework.Assert.AreEqual(561.658, new ClipperBridge().LongRectCalculateWidth(longRect), 0.001f); } [NUnit.Framework.Test] public virtual void LongRectHeightTest() { IntRect longRect = new IntRect(14900000000000000L, 21275000000000000L, 71065802001953128L, 71075000000000000L ); - NUnit.Framework.Assert.AreEqual(498, ClipperBridge.LongRectCalculateHeight(longRect), 0.001f); + NUnit.Framework.Assert.AreEqual(498, new ClipperBridge().LongRectCalculateHeight(longRect), 0.001f); + } + + [NUnit.Framework.Test] + public virtual void DynamicFloatMultiplierCalculationsSmallValuesTest() { + Point[] points = new Point[] { new Point(1e-10, 0), new Point(0, 1e-13) }; + NUnit.Framework.Assert.AreEqual(1.8014398509481984e26, new ClipperBridge(points).GetFloatMultiplier(), 0e+10 + ); + } + + [NUnit.Framework.Test] + public virtual void DynamicFloatMultiplierCalculationsBigValuesTest() { + Point[] points = new Point[] { new Point(1e+11, 10), new Point(10, 1e+10) }; + NUnit.Framework.Assert.AreEqual(180143, new ClipperBridge(points).GetFloatMultiplier(), 0.001f); + } + + [NUnit.Framework.Test] + public virtual void SmallFloatMultiplierCoefficientTest() { + Point[] points = new Point[] { new Point(1e-10, 1e+10) }; + NUnit.Framework.Assert.AreEqual(new IntPoint(0, 18014390000000000L), new ClipperBridge(points).ConvertToLongPoints + (JavaUtil.ArraysAsList(points))[0]); } private bool AreShapesEqual(IShape expected, IShape actual) { diff --git a/itext/itext.kernel/itext/kernel/pdf/canvas/parser/ParserGraphicsState.cs b/itext/itext.kernel/itext/kernel/pdf/canvas/parser/ParserGraphicsState.cs index f5fa2a54e1..506c22c1ab 100644 --- a/itext/itext.kernel/itext/kernel/pdf/canvas/parser/ParserGraphicsState.cs +++ b/itext/itext.kernel/itext/kernel/pdf/canvas/parser/ParserGraphicsState.cs @@ -62,6 +62,7 @@ public override void UpdateCtm(Matrix newCtm) { /// Intersects the current clipping path with the given path. /// /// Intersects the current clipping path with the given path. + /// /// Note: Coordinates of the given path should be in /// the transformed user space. /// @@ -80,17 +81,19 @@ public virtual void Clip(Path path, int fillingRule) { Path pathCopy = new Path(path); pathCopy.CloseAllSubpaths(); Clipper clipper = new Clipper(); - ClipperBridge.AddPath(clipper, clippingPath, PolyType.SUBJECT); - ClipperBridge.AddPath(clipper, pathCopy, PolyType.CLIP); + ClipperBridge clipperBridge = new ClipperBridge(clippingPath, pathCopy); + clipperBridge.AddPath(clipper, clippingPath, PolyType.SUBJECT); + clipperBridge.AddPath(clipper, pathCopy, PolyType.CLIP); PolyTree resultTree = new PolyTree(); clipper.Execute(ClipType.INTERSECTION, resultTree, PolyFillType.NON_ZERO, ClipperBridge.GetFillType(fillingRule )); - clippingPath = ClipperBridge.ConvertToPath(resultTree); + clippingPath = clipperBridge.ConvertToPath(resultTree); } /// Getter for the current clipping path. /// /// Getter for the current clipping path. + /// /// Note: The returned clipping path is in the transformed user space, so /// if you want to get it in default user space, apply transformation matrix ( /// @@ -104,6 +107,7 @@ public virtual Path GetClippingPath() { /// Sets the current clipping path to the specified path. /// /// Sets the current clipping path to the specified path. + /// /// Note:This method doesn't modify existing clipping path, /// it simply replaces it with the new one instead. /// diff --git a/itext/itext.kernel/itext/kernel/pdf/canvas/parser/clipperlib/ClipperBridge.cs b/itext/itext.kernel/itext/kernel/pdf/canvas/parser/clipperlib/ClipperBridge.cs index 822f43841d..54a4d5d66b 100644 --- a/itext/itext.kernel/itext/kernel/pdf/canvas/parser/clipperlib/ClipperBridge.cs +++ b/itext/itext.kernel/itext/kernel/pdf/canvas/parser/clipperlib/ClipperBridge.cs @@ -28,12 +28,12 @@ You should have received a copy of the GNU Affero General Public License namespace iText.Kernel.Pdf.Canvas.Parser.ClipperLib { /// - /// 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. /// /// - /// 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. /// /// For example: /// @@ -55,6 +55,8 @@ namespace iText.Kernel.Pdf.Canvas.Parser.ClipperLib { /// /// public sealed class ClipperBridge { + private const 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 @@ -64,15 +66,97 @@ public sealed class ClipperBridge { /// 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. + /// + /// Note that if this value is specified, it will be used for all ClipperBridge instances and + /// dynamic float multiplier calculation will be disabled. + /// + public static double? floatMultiplier; + + private double approximatedFloatMultiplier = Math.Pow(10, 14); + + /// + /// Creates new + /// + /// instance with default float multiplier value which is 10^14. + /// + /// + /// Creates new + /// + /// instance with default float multiplier value which is 10^14. + /// + /// 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 + /// + /// is specified, it will be used for all ClipperBridge + /// instances and default value will be ignored. /// - public static double floatMultiplier = Math - //TODO DEVSIX-5770 make this constant a single non-static configuration - .Pow(10, 14); + public ClipperBridge() { + } + + // Empty constructor. + /// + /// Creates new + /// + /// instance with adjusted float multiplier value. + /// + /// + /// Creates new + /// + /// instance with adjusted float multiplier value. This instance will work + /// correctly with the provided paths only. + /// + /// 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 + /// + /// is + /// specified, it will be used for all ClipperBridge instances and automatic calculation won't work. + /// + /// paths to calculate multiplier coefficient to convert floating point numbers into fixed point numbers + /// + public ClipperBridge(params Path[] paths) { + if (floatMultiplier == null) { + IList pointsList = new List(); + foreach (Path path in paths) { + foreach (Subpath subpath in path.GetSubpaths()) { + if (!subpath.IsSinglePointClosed() && !subpath.IsSinglePointOpen()) { + pointsList.AddAll(subpath.GetPiecewiseLinearApproximation()); + } + } + } + CalculateFloatMultiplier(pointsList.ToArray(new Point[0])); + } + } - private ClipperBridge() { + /// + /// Creates new + /// + /// instance with adjusted float multiplier value. + /// + /// + /// Creates new + /// + /// instance with adjusted float multiplier value. This instance will work + /// correctly with the provided point only. + /// + /// 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 + /// + /// is + /// specified, it will be used for all ClipperBridge instances and automatic calculation won't work. + /// + /// + /// points to calculate multiplier coefficient to convert floating point numbers + /// into fixed point numbers + /// + public ClipperBridge(params Point[][] points) { + if (floatMultiplier == null) { + CalculateFloatMultiplier(points); + } } - //empty constructor /// /// Converts Clipper library /// @@ -90,7 +174,7 @@ private ClipperBridge() { /// /// object /// - public static Path ConvertToPath(PolyTree result) { + public Path ConvertToPath(PolyTree result) { Path path = new Path(); PolyNode node = result.GetFirst(); while (node != null) { @@ -122,7 +206,7 @@ public static Path ConvertToPath(PolyTree result) { /// See /// . /// - public static void AddPath(Clipper clipper, Path path, PolyType polyType) { + public void AddPath(Clipper clipper, Path path, PolyType polyType) { foreach (Subpath subpath in path.GetSubpaths()) { if (!subpath.IsSinglePointClosed() && !subpath.IsSinglePointOpen()) { IList linearApproxPoints = subpath.GetPiecewiseLinearApproximation(); @@ -186,7 +270,7 @@ public static void AddPath(Clipper clipper, Path path, PolyType polyType) { /// /// s of the path. /// - public static IList AddPath(ClipperOffset offset, Path path, JoinType joinType, EndType endType) { + public IList AddPath(ClipperOffset offset, Path path, JoinType joinType, EndType endType) { IList degenerateSubpaths = new List(); foreach (Subpath subpath in path.GetSubpaths()) { if (subpath.IsDegenerate()) { @@ -226,10 +310,10 @@ public static IList AddPath(ClipperOffset offset, Path path, JoinType j /// /// objects. /// - public static IList ConvertToFloatPoints(IList points) { + public IList ConvertToFloatPoints(IList points) { IList convertedPoints = new List(points.Count); foreach (IntPoint point in points) { - convertedPoints.Add(new Point(point.X / floatMultiplier, point.Y / floatMultiplier)); + convertedPoints.Add(new Point(point.X / GetFloatMultiplier(), point.Y / GetFloatMultiplier())); } return convertedPoints; } @@ -251,10 +335,11 @@ public static IList ConvertToFloatPoints(IList points) { /// /// objects. /// - public static IList ConvertToLongPoints(IList points) { + public IList ConvertToLongPoints(IList points) { IList convertedPoints = new List(points.Count); foreach (Point point in points) { - convertedPoints.Add(new IntPoint(floatMultiplier * point.GetX(), floatMultiplier * point.GetY())); + convertedPoints.Add(new IntPoint(GetFloatMultiplier() * point.GetX(), GetFloatMultiplier() * point.GetY()) + ); } return convertedPoints; } @@ -372,7 +457,7 @@ public static PolyFillType GetFillType(int fillingRule) { /// path is a subject of clipping or a part of the clipping polygon. /// /// true if polygon path was successfully added, false otherwise. - public static bool AddPolygonToClipper(Clipper clipper, Point[] polyVertices, PolyType polyType) { + public bool AddPolygonToClipper(Clipper clipper, Point[] polyVertices, PolyType polyType) { return clipper.AddPath(new List(ConvertToLongPoints(new List(JavaUtil.ArraysAsList(polyVertices )))), polyType, true); } @@ -418,7 +503,7 @@ public static bool AddPolygonToClipper(Clipper clipper, Point[] polyVertices, Po /// to clipper path and added to the clipper instance. /// /// true if polyline path was successfully added, false otherwise. - public static bool AddPolylineSubjectToClipper(Clipper clipper, Point[] lineVertices) { + public bool AddPolylineSubjectToClipper(Clipper clipper, Point[] lineVertices) { return clipper.AddPath(new List(ConvertToLongPoints(new List(JavaUtil.ArraysAsList(lineVertices )))), PolyType.SUBJECT, false); } @@ -434,9 +519,8 @@ public static bool AddPolylineSubjectToClipper(Clipper clipper, Point[] lineVert /// object representing the rectangle. /// /// the width of the rectangle. - public static float LongRectCalculateWidth(IntRect rect) { - return (float)(Math.Abs(rect.left - rect.right) / iText.Kernel.Pdf.Canvas.Parser.ClipperLib.ClipperBridge. - floatMultiplier); + public float LongRectCalculateWidth(IntRect rect) { + return (float)(Math.Abs(rect.left - rect.right) / GetFloatMultiplier()); } /// @@ -450,13 +534,21 @@ public static float LongRectCalculateWidth(IntRect rect) { /// object representing the rectangle. /// /// the height of the rectangle. - public static float LongRectCalculateHeight(IntRect rect) { - return (float)(Math.Abs(rect.top - rect.bottom) / iText.Kernel.Pdf.Canvas.Parser.ClipperLib.ClipperBridge. - floatMultiplier); + public float LongRectCalculateHeight(IntRect rect) { + return (float)(Math.Abs(rect.top - rect.bottom) / GetFloatMultiplier()); + } + + /// Gets multiplier coefficient for converting our floating point numbers into fixed point numbers. + /// multiplier coefficient for converting our floating point numbers into fixed point numbers + public double GetFloatMultiplier() { + if (floatMultiplier == null) { + return approximatedFloatMultiplier; + } + return (double)floatMultiplier; } //\cond DO_NOT_DOCUMENT - internal static void AddContour(Path path, IList contour, bool close) { + internal void AddContour(Path path, IList contour, bool close) { IList floatContour = ConvertToFloatPoints(contour); Point point = floatContour[0]; path.MoveTo((float)point.GetX(), (float)point.GetY()); @@ -469,5 +561,20 @@ internal static void AddContour(Path path, IList contour, bool close) } } //\endcond + + private void CalculateFloatMultiplier(params Point[][] points) { + double maxPoint = 0; + foreach (Point[] pointsArray in points) { + foreach (Point point in 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); + } + } } } diff --git a/port-hash b/port-hash index 9413a40b2c..293085fcc7 100644 --- a/port-hash +++ b/port-hash @@ -1 +1 @@ -e84bb3e25bfeda609158e655bd754ec159ce6db3 +1bb85b3f24c5e613afa2a6219a459939b5dcb9ef