#12 performance optimizations for type 1 fonts and other tweaks

This commit is contained in:
Eliot Jones
2018-11-25 11:37:00 +00:00
parent 87fccbbadc
commit 3f3badb7b4
5 changed files with 143 additions and 59 deletions

View File

@@ -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++)
{

View File

@@ -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<PdfPoint, double> 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 = (1t)^3*P_1 + 3(1t)^2*t*P_2 + 3(1t)*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;
}

View File

@@ -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 @@
/// </summary>
internal class Type1FontSimple : IFont
{
private readonly Dictionary<int, CharacterBoundingBox> cachedBoundingBoxes = new Dictionary<int, CharacterBoundingBox>();
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)

View File

@@ -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);
}
}
}

View File

@@ -1,7 +1,5 @@
namespace UglyToad.PdfPig.Geometry
{
using System;
/// <summary>
/// A rectangle in a PDF file.
/// </summary>
@@ -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;
}
/// <summary>
/// To string override.
/// </summary>