mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-09-22 12:09:50 +08:00
#12 performance optimizations for type 1 fonts and other tweaks
This commit is contained in:
@@ -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++)
|
||||
{
|
||||
|
@@ -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 = (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;
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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>
|
||||
|
Reference in New Issue
Block a user