From 3f3badb7b4832621b920b4506d37ae57c5e32fdb Mon Sep 17 00:00:00 2001 From: Eliot Jones Date: Sun, 25 Nov 2018 11:37:00 +0000 Subject: [PATCH] #12 performance optimizations for type 1 fonts and other tweaks --- .../Core/TransformationMatrix.cs | 20 ++++-- src/UglyToad.PdfPig/Fonts/CharacterPath.cs | 66 ++++++++++++++----- .../Fonts/Simple/Type1FontSimple.cs | 42 ++++++------ .../Fonts/Type1/Type1FontProgram.cs | 29 ++++---- src/UglyToad.PdfPig/Geometry/PdfRectangle.cs | 45 +++++++++++-- 5 files changed, 143 insertions(+), 59 deletions(-) diff --git a/src/UglyToad.PdfPig/Core/TransformationMatrix.cs b/src/UglyToad.PdfPig/Core/TransformationMatrix.cs index 2f40b98c..bd3cab85 100644 --- a/src/UglyToad.PdfPig/Core/TransformationMatrix.cs +++ b/src/UglyToad.PdfPig/Core/TransformationMatrix.cs @@ -93,6 +93,14 @@ return new PdfPoint(x, y); } + [Pure] + public decimal TransformX(decimal x) + { + var xt = A * x + C * 0 + E; + + return xt; + } + [Pure] public PdfVector Transform(PdfVector original) { @@ -106,10 +114,10 @@ public PdfRectangle Transform(PdfRectangle original) { return new PdfRectangle( - Transform(original.TopLeft.ToVector()), - Transform(original.TopRight.ToVector()), - Transform(original.BottomLeft.ToVector()), - Transform(original.BottomRight.ToVector()) + Transform(original.TopLeft), + Transform(original.TopRight), + Transform(original.BottomLeft), + Transform(original.BottomRight) ); } @@ -151,9 +159,11 @@ for (int i = 0; i < Rows; i++) { + var rowIndexPart = i * Rows; + for (int j = 0; j < Columns; j++) { - var index = (i * Rows) + j; + var index = rowIndexPart + j; for (int x = 0; x < Rows; x++) { diff --git a/src/UglyToad.PdfPig/Fonts/CharacterPath.cs b/src/UglyToad.PdfPig/Fonts/CharacterPath.cs index 386b7948..17a501c1 100644 --- a/src/UglyToad.PdfPig/Fonts/CharacterPath.cs +++ b/src/UglyToad.PdfPig/Fonts/CharacterPath.cs @@ -234,31 +234,54 @@ namespace UglyToad.PdfPig.Fonts public PdfRectangle? GetBoundingRectangle() { - var minX = Math.Min(StartPoint.X, EndPoint.X); - var maxX = Math.Max(StartPoint.X, EndPoint.X); + // Optimised + double minX; + double maxX; + if (StartPoint.X <= EndPoint.X) + { + minX = (double) StartPoint.X; + maxX = (double) EndPoint.X; + } + else + { + minX = (double)EndPoint.X; + maxX = (double)StartPoint.X; + } - var minY = Math.Min(StartPoint.Y, EndPoint.Y); - var maxY = Math.Max(StartPoint.Y, EndPoint.Y); + double minY; + double maxY; + if (StartPoint.Y <= EndPoint.Y) + { + minY = (double)StartPoint.Y; + maxY = (double)EndPoint.Y; + } + else + { + minY = (double)EndPoint.Y; + maxY = (double)StartPoint.Y; + } - if (TrySolveQuadratic(x => (double)x.X, minX, maxX, out var xSolutions)) + if (TrySolveQuadratic(true, minX, maxX, out var xSolutions)) { minX = xSolutions.min; maxX = xSolutions.max; } - if (TrySolveQuadratic(x => (double)x.Y, minY, maxY, out var ySolutions)) + if (TrySolveQuadratic(false, minY, maxY, out var ySolutions)) { minY = ySolutions.min; maxY = ySolutions.max; } - return new PdfRectangle(minX, minY, maxX, maxY); + return new PdfRectangle((decimal)minX, (decimal)minY, (decimal)maxX, (decimal)maxY); } - private bool TrySolveQuadratic(Func valueAccessor, decimal currentMin, decimal currentMax, out (decimal min, decimal max) solutions) + private bool TrySolveQuadratic(bool isX, double currentMin, double currentMax, out (double min, double max) solutions) { - solutions = default((decimal, decimal)); + solutions = default((double, double)); + + // This method has been optimised for performance by eliminating calls to Math. // Given k points the general form is: // P = (1-t)^(k - i - 1)*t^(i)*P_i @@ -271,10 +294,10 @@ namespace UglyToad.PdfPig.Fonts // P' = 3da(1-t)^2 + 6db(1-t)t + 3dct^2 // P' = 3da - 3dat - 3dat + 3dat^2 + 6dbt - 6dbt^2 + 3dct^2 // P' = (3da - 6db + 3dc)t^2 + (6db - 3da - 3da)t + 3da - var p1 = valueAccessor(StartPoint); - var p2 = valueAccessor(FirstControlPoint); - var p3 = valueAccessor(SecondControlPoint); - var p4 = valueAccessor(EndPoint); + var p1 = (double)( isX ? StartPoint.X : StartPoint.Y); + var p2 = (double)(isX ? FirstControlPoint.X : FirstControlPoint.Y); + var p3 = (double)(isX ? SecondControlPoint.X : SecondControlPoint.Y); + var p4 = (double)(isX ? EndPoint.X : EndPoint.Y); var threeda = 3 * (p2 - p1); var sixdb = 6 * (p3 - p2); @@ -294,12 +317,15 @@ namespace UglyToad.PdfPig.Fonts return false; } - var t1 = (-b + Math.Sqrt(sqrtable)) / (2 * a); - var t2 = (-b - Math.Sqrt(sqrtable)) / (2 * a); + var sqrt = Math.Sqrt(sqrtable); + var divisor = 2 * a; + + var t1 = (-b + sqrt) / divisor; + var t2 = (-b - sqrt) / divisor; if (t1 >= 0 && t1 <= 1) { - var sol1 = (decimal)ValueWithT(p1, p2, p3, p4, t1); + var sol1 = ValueWithT(p1, p2, p3, p4, t1); if (sol1 < currentMin) { currentMin = sol1; @@ -313,7 +339,7 @@ namespace UglyToad.PdfPig.Fonts if (t2 >= 0 && t2 <= 1) { - var sol2 = (decimal)ValueWithT(p1, p2, p3, p4, t2); + var sol2 = ValueWithT(p1, p2, p3, p4, t2); if (sol2 < currentMin) { currentMin = sol2; @@ -333,7 +359,11 @@ namespace UglyToad.PdfPig.Fonts private static double ValueWithT(double p1, double p2, double p3, double p4, double t) { // P = (1−t)^3*P_1 + 3(1−t)^2*t*P_2 + 3(1−t)*t^2*P_3 + t^3*P_4 - var p = (Math.Pow(1 - t, 3) * p1) + (3 * Math.Pow(1 - t, 2) * t * p2) + (3 * (1 - t) * Math.Pow(t, 2) * p3) + (Math.Pow(t, 3) * p4); + var oneMinusT = 1 - t; + var p = ((oneMinusT * oneMinusT * oneMinusT) * p1) + + (3 * (oneMinusT * oneMinusT) * t * p2) + + (3 * oneMinusT * (t * t) * p3) + + ((t * t * t) * p4); return p; } diff --git a/src/UglyToad.PdfPig/Fonts/Simple/Type1FontSimple.cs b/src/UglyToad.PdfPig/Fonts/Simple/Type1FontSimple.cs index 5ea404a6..53f1b0bd 100644 --- a/src/UglyToad.PdfPig/Fonts/Simple/Type1FontSimple.cs +++ b/src/UglyToad.PdfPig/Fonts/Simple/Type1FontSimple.cs @@ -1,6 +1,7 @@ namespace UglyToad.PdfPig.Fonts.Simple { using System; + using System.Collections.Generic; using Cmap; using CompactFontFormat; using Composite; @@ -18,6 +19,8 @@ /// internal class Type1FontSimple : IFont { + private readonly Dictionary cachedBoundingBoxes = new Dictionary(); + private readonly int firstChar; private readonly int lastChar; @@ -33,7 +36,7 @@ private readonly ToUnicodeCMap toUnicodeCMap; - private readonly TransformationMatrix fontMatrix = TransformationMatrix.FromValues(0.001m, 0, 0, 0.001m, 0, 0); + private readonly TransformationMatrix fontMatrix; public NameToken Name { get; } @@ -50,6 +53,12 @@ this.encoding = encoding; this.fontProgram = fontProgram; this.toUnicodeCMap = new ToUnicodeCMap(toUnicodeCMap); + + var matrix = TransformationMatrix.FromValues(0.001m, 0, 0, 0.001m, 0, 0); + fontProgram?.Match(x => matrix = x.GetFontTransformationMatrix(), x => { matrix = x.GetFontTransformationMatrix(); }); + + fontMatrix = matrix; + Name = name; } @@ -107,31 +116,26 @@ public CharacterBoundingBox GetBoundingBox(int characterCode) { + if (cachedBoundingBoxes.TryGetValue(characterCode, out var box)) + { + return box; + } + var boundingBox = GetBoundingBoxInGlyphSpace(characterCode); - var matrix = GetFontMatrixInternal(); + var matrix = fontMatrix; boundingBox = matrix.Transform(boundingBox); - var width = matrix.Transform(new PdfVector(widths[characterCode - firstChar], 0)).X; + var width = matrix.TransformX(widths[characterCode - firstChar]); - return new CharacterBoundingBox(boundingBox, width); + var result = new CharacterBoundingBox(boundingBox, width); + + cachedBoundingBoxes[characterCode] = result; + + return result; } - - private TransformationMatrix GetFontMatrixInternal() - { - if (fontProgram == null) - { - return fontMatrix; - } - - var matrix = default(TransformationMatrix); - - fontProgram.Match(x => { matrix = fontMatrix; }, x => { matrix = x.GetFontTransformationMatrix(); }); - - return matrix; - } - + private PdfRectangle GetBoundingBoxInGlyphSpace(int characterCode) { if (characterCode < firstChar || characterCode > lastChar) diff --git a/src/UglyToad.PdfPig/Fonts/Type1/Type1FontProgram.cs b/src/UglyToad.PdfPig/Fonts/Type1/Type1FontProgram.cs index b80cc7e7..59e2bfb1 100644 --- a/src/UglyToad.PdfPig/Fonts/Type1/Type1FontProgram.cs +++ b/src/UglyToad.PdfPig/Fonts/Type1/Type1FontProgram.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Diagnostics; using CharStrings; + using Core; using Geometry; using Tokens; using Util.JetBrains.Annotations; @@ -65,17 +66,6 @@ var glyph = CharStrings.Generate(b); var bbox = glyph.GetBoundingRectangle(); - if (!bbox.HasValue) - { - return null; - } - - if (Debugger.IsAttached) - { - var full = glyph.ToFullSvg(); - Console.WriteLine(full); - } - return bbox; } @@ -83,5 +73,22 @@ { return CharStrings.CharStrings.ContainsKey(name); } + + public TransformationMatrix GetFontTransformationMatrix() + { + if (FontMatrix == null || FontMatrix.Data.Count != 6) + { + return TransformationMatrix.FromValues(0.001m, 0, 0, 0.001m, 0, 0);; + } + + var a = ((NumericToken) FontMatrix.Data[0]).Data; + var b = ((NumericToken) FontMatrix.Data[1]).Data; + var c = ((NumericToken) FontMatrix.Data[2]).Data; + var d = ((NumericToken) FontMatrix.Data[3]).Data; + var e = ((NumericToken) FontMatrix.Data[4]).Data; + var f = ((NumericToken) FontMatrix.Data[5]).Data; + + return TransformationMatrix.FromValues(a, b, c, d, e, f); + } } } \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Geometry/PdfRectangle.cs b/src/UglyToad.PdfPig/Geometry/PdfRectangle.cs index 8fe3ec68..97bfa6ea 100644 --- a/src/UglyToad.PdfPig/Geometry/PdfRectangle.cs +++ b/src/UglyToad.PdfPig/Geometry/PdfRectangle.cs @@ -1,7 +1,5 @@ namespace UglyToad.PdfPig.Geometry { - using System; - /// /// A rectangle in a PDF file. /// @@ -71,11 +69,32 @@ internal PdfRectangle(short x1, short y1, short x2, short y2) : this((decimal) x1, y1, x2, y2) { } internal PdfRectangle(decimal x1, decimal y1, decimal x2, decimal y2) { - var bottom = Math.Min(y1, y2); - var top = Math.Max(y1, y2); + decimal bottom; + decimal top; - var left = Math.Min(x1, x2); - var right = Math.Max(x1, x2); + if (y1 <= y2) + { + bottom = y1; + top = y2; + } + else + { + bottom = y2; + top = y1; + } + + decimal left; + decimal right; + if (x1 <= x2) + { + left = x1; + right = x2; + } + else + { + left = x2; + right = x1; + } TopLeft = new PdfPoint(left, top); TopRight = new PdfPoint(right, top); @@ -103,6 +122,20 @@ Area = Width * Height; } + internal PdfRectangle(PdfPoint topLeft, PdfPoint topRight, PdfPoint bottomLeft, PdfPoint bottomRight) + { + TopLeft = topLeft; + TopRight = topRight; + + BottomLeft = bottomLeft; + BottomRight = bottomRight; + + Width = bottomRight.X - bottomLeft.X; + Height = topLeft.Y - bottomLeft.Y; + + Area = Width * Height; + } + /// /// To string override. ///