unify truetype glyphs to a single class. build composite glyphs from elements

This commit is contained in:
Eliot Jones
2018-04-14 22:16:26 +01:00
parent 731a1a3956
commit 8def7d7e0b
10 changed files with 1284 additions and 139 deletions

View File

@@ -20,7 +20,7 @@
public static AssertablePositionData Parse(string line)
{
var parts = line.Split('\t', StringSplitOptions.None);
var parts = line.Split('\t');
if (parts.Length < 6)
{
@@ -36,7 +36,8 @@
Width = decimal.Parse(parts[2]),
Text = parts[3],
FontSize = decimal.Parse(parts[4]),
FontName = parts[5]
FontName = parts[5],
Height = height
};
}
}

View File

@@ -1,20 +0,0 @@
namespace UglyToad.PdfPig.Fonts.TrueType.Glyphs
{
using Geometry;
internal class CompositeGlyphDescription : IGlyphDescription
{
public bool IsSimple { get; } = false;
public SimpleGlyphDescription SimpleGlyph { get; } = null;
public CompositeGlyphDescription CompositeGlyph => this;
public PdfRectangle GlyphBounds { get; }
public IGlyphDescription DeepClone()
{
return new CompositeGlyphDescription();
}
}
}

View File

@@ -1,25 +0,0 @@
namespace UglyToad.PdfPig.Fonts.TrueType.Glyphs
{
using Geometry;
internal class EmptyGlyph : IGlyphDescription
{
public bool IsSimple { get; } = true;
public SimpleGlyphDescription SimpleGlyph => new SimpleGlyphDescription(new byte[0], new int[0], new GlyphPoint[0], GlyphBounds);
public CompositeGlyphDescription CompositeGlyph { get; } = null;
public PdfRectangle GlyphBounds { get; }
public EmptyGlyph(PdfRectangle glyphBounds)
{
GlyphBounds = glyphBounds;
}
public IGlyphDescription DeepClone()
{
return new EmptyGlyph(GlyphBounds);
}
}
}

View File

@@ -0,0 +1,125 @@
namespace UglyToad.PdfPig.Fonts.TrueType.Glyphs
{
using System;
using Geometry;
internal class Glyph : IGlyphDescription
{
/// <summary>
/// The bounding rectangle for the character.
/// </summary>
public PdfRectangle Bounds { get; }
/// <summary>
/// The bytes of the instructions for this glyph.
/// </summary>
public byte[] Instructions { get; }
/// <summary>
/// An array of the last points of each contour.
/// </summary>
public int[] EndPointsOfContours { get; }
public GlyphPoint[] Points { get; }
public bool IsSimple { get; }
public Glyph(bool isSimple, byte[] instructions, int[] endPointsOfContours, GlyphPoint[] points,
PdfRectangle bounds)
{
IsSimple = isSimple;
Instructions = instructions;
EndPointsOfContours = endPointsOfContours;
Points = points;
Bounds = bounds;
}
public static IGlyphDescription Empty(PdfRectangle bounds)
{
return new Glyph(true, new byte[0], new int[0], new GlyphPoint[0], bounds);
}
public IGlyphDescription DeepClone()
{
var clonedInstructions = new byte[Instructions.Length];
Array.Copy(Instructions, clonedInstructions, Instructions.Length);
var clonedEndPoints = new int[EndPointsOfContours.Length];
Array.Copy(EndPointsOfContours, clonedEndPoints, EndPointsOfContours.Length);
var clonedPoints = new GlyphPoint[Points.Length];
Array.Copy(Points, clonedPoints, Points.Length);
return new Glyph(false, clonedInstructions, clonedEndPoints, clonedPoints, Bounds);
}
public IGlyphDescription Merge(IGlyphDescription glyph)
{
var newPoints = MergePoints(glyph);
var newEndpoints = MergeContourEndPoints(glyph);
return new Glyph(false, Instructions, newEndpoints, newPoints, Bounds);
}
private GlyphPoint[] MergePoints(IGlyphDescription glyph)
{
var newPoints = new GlyphPoint[Points.Length + glyph.Points.Length];
for (int i = 0; i < Points.Length; i++)
{
newPoints[i] = Points[i];
}
for (int i = 0; i < glyph.Points.Length; i++)
{
newPoints[i + Points.Length] = glyph.Points[i];
}
return newPoints;
}
private int[] MergeContourEndPoints(IGlyphDescription glyph)
{
var destinationLastEndPoint = EndPointsOfContours[EndPointsOfContours.Length - 1] + 1;
var endPoints = new int[EndPointsOfContours.Length + glyph.EndPointsOfContours.Length];
for (var i = 0; i < EndPointsOfContours.Length; i++)
{
endPoints[i] = EndPointsOfContours[i];
}
for (var i = 0; i < glyph.EndPointsOfContours.Length; i++)
{
endPoints[i + EndPointsOfContours.Length] = glyph.EndPointsOfContours[i] + destinationLastEndPoint;
}
return endPoints;
}
public IGlyphDescription Transform(PdfMatrix3By2 matrix)
{
var newPoints = new GlyphPoint[Points.Length];
for (var i = Points.Length - 1; i >= 0; i--)
{
var point = Points[i];
var scaled = matrix.ScaleAndRotate(point.Point);
scaled = matrix.Translate(scaled);
newPoints[i] = new GlyphPoint(scaled, point.IsOnCurve);
}
return new Glyph(IsSimple, Instructions, EndPointsOfContours, newPoints, Bounds);
}
public override string ToString()
{
var type = IsSimple ? "S" : "C";
return $"{type}: Width {Bounds.Width}, Height: {Bounds.Height}, Points: {Points.Length}";
}
}
}

View File

@@ -2,16 +2,28 @@
{
using Geometry;
internal interface IGlyphDescription
internal interface IGlyphDescription : IMergeableGlyph, ITransformableGlyph
{
bool IsSimple { get; }
SimpleGlyphDescription SimpleGlyph { get; }
PdfRectangle Bounds { get; }
CompositeGlyphDescription CompositeGlyph { get; }
byte[] Instructions { get; }
PdfRectangle GlyphBounds { get; }
int[] EndPointsOfContours { get; }
GlyphPoint[] Points { get; }
IGlyphDescription DeepClone();
}
internal interface IMergeableGlyph
{
IGlyphDescription Merge(IGlyphDescription glyph);
}
internal interface ITransformableGlyph
{
IGlyphDescription Transform(PdfMatrix3By2 matrix);
}
}

View File

@@ -1,54 +0,0 @@
namespace UglyToad.PdfPig.Fonts.TrueType.Glyphs
{
using System;
using Geometry;
internal class SimpleGlyphDescription : IGlyphDescription
{
/// <summary>
/// The bounding rectangle for the character.
/// </summary>
public PdfRectangle GlyphBounds { get; }
/// <summary>
/// The total number of bytes for instructions.
/// </summary>
public byte[] Instructions { get; }
/// <summary>
/// An array of the last points of each contour.
/// </summary>
public int[] EndPointsOfContours { get; }
public GlyphPoint[] Points { get; set; }
public SimpleGlyphDescription(byte[] instructions, int[] endPointsOfContours, GlyphPoint[] points,
PdfRectangle bounds)
{
Instructions = instructions;
EndPointsOfContours = endPointsOfContours;
Points = points;
GlyphBounds = bounds;
}
public bool IsSimple { get; } = true;
public SimpleGlyphDescription SimpleGlyph => this;
public CompositeGlyphDescription CompositeGlyph { get; } = null;
public IGlyphDescription DeepClone()
{
var clonedInstructions = new byte[Instructions.Length];
Array.Copy(Instructions, clonedInstructions, Instructions.Length);
var clonedEndPoints = new int[EndPointsOfContours.Length];
Array.Copy(EndPointsOfContours, clonedEndPoints, EndPointsOfContours.Length);
var clonedPoints = new GlyphPoint[Points.Length];
Array.Copy(Points, clonedPoints, Points.Length);
return new SimpleGlyphDescription(clonedInstructions, clonedEndPoints, clonedPoints, GlyphBounds);
}
}
}

View File

@@ -39,7 +39,7 @@
var glyphs = new IGlyphDescription[glyphCount];
var emptyGlyph = new EmptyGlyph(tableRegister.HeaderTable.Bounds);
var emptyGlyph = Glyph.Empty(tableRegister.HeaderTable.Bounds);
var compositeLocations = new Dictionary<int, TemporaryCompositeLocation>();
@@ -77,13 +77,13 @@
// Build composite glyphs by combining simple and other composite glyphs.
foreach (var compositeLocation in compositeLocations)
{
glyphs[compositeLocation.Key] = ReadCompositeGlyph(data, compositeLocation.Value, compositeLocations, glyphs);
glyphs[compositeLocation.Key] = ReadCompositeGlyph(data, compositeLocation.Value, compositeLocations, glyphs, emptyGlyph);
}
return new GlyphDataTable(table, glyphs);
}
private static SimpleGlyphDescription ReadSimpleGlyph(TrueTypeDataBytes data, short contourCount, PdfRectangle bounds)
private static Glyph ReadSimpleGlyph(TrueTypeDataBytes data, short contourCount, PdfRectangle bounds)
{
var endPointsOfContours = data.ReadUnsignedShortArray(contourCount);
@@ -112,10 +112,11 @@
points[i] = new GlyphPoint(xCoordinates[i], yCoordinates[i], isOnCurve);
}
return new SimpleGlyphDescription(instructions, endPointsOfContours, points, bounds);
return new Glyph(true, instructions, endPointsOfContours, points, bounds);
}
private static CompositeGlyphDescription ReadCompositeGlyph(TrueTypeDataBytes data, TemporaryCompositeLocation compositeLocation, Dictionary<int, TemporaryCompositeLocation> compositeLocations, IGlyphDescription[] glyphs)
private static IGlyphDescription ReadCompositeGlyph(TrueTypeDataBytes data, TemporaryCompositeLocation compositeLocation, Dictionary<int, TemporaryCompositeLocation> compositeLocations, IGlyphDescription[] glyphs,
IGlyphDescription emptyGlyph)
{
bool HasFlag(CompositeGlyphFlags value, CompositeGlyphFlags target)
{
@@ -125,7 +126,8 @@
data.Seek(compositeLocation.Position);
var components = new List<CompositeComponent>();
// First recursively find all components and ensure they are available.
CompositeGlyphFlags flags;
do
{
@@ -141,13 +143,11 @@
throw new InvalidOperationException($"The composite glyph required a contour at index {glyphIndex} but there was no simple or composite glyph at this location.");
}
childGlyph = ReadCompositeGlyph(data, missingComposite, compositeLocations, glyphs);
childGlyph = ReadCompositeGlyph(data, missingComposite, compositeLocations, glyphs, emptyGlyph);
glyphs[glyphIndex] = childGlyph;
}
var cloned = childGlyph.DeepClone();
short arg1, arg2;
if (HasFlag(flags, CompositeGlyphFlags.Args1And2AreWords))
{
@@ -194,14 +194,27 @@
} while (HasFlag(flags, CompositeGlyphFlags.MoreComponents));
return new CompositeGlyphDescription();
}
// Now build the final glyph from the components.
IGlyphDescription builderGlyph = null;
foreach (var component in components)
{
var glyph = glyphs[component.Index];
private static decimal ReadTwoFourteenFormat(TrueTypeDataBytes data)
{
const decimal divisor = 1 << 14;
var transformed = glyph.Transform(component.Transformation);
return data.ReadSignedShort() / divisor;
if (builderGlyph == null)
{
builderGlyph = transformed;
}
else
{
builderGlyph = builderGlyph.Merge(transformed);
}
}
builderGlyph = builderGlyph ?? emptyGlyph;
return new Glyph(false, builderGlyph.Instructions, builderGlyph.EndPointsOfContours, builderGlyph.Points, compositeLocation.Bounds);
}
private static SimpleGlyphFlags[] ReadFlags(TrueTypeDataBytes data, int pointCount)
@@ -248,6 +261,13 @@
return xs;
}
private static decimal ReadTwoFourteenFormat(TrueTypeDataBytes data)
{
const decimal divisor = 1 << 14;
return data.ReadSignedShort() / divisor;
}
/// <summary>
/// Stores the composite glyph information we read when initially scanning the glyph table.
/// Once we have all composite glyphs we can start building them from simple glyphs.
@@ -258,27 +278,18 @@
/// Stores the position after reading the contour count and bounds.
/// </summary>
public long Position { get; }
/// <summary>
/// The bounds we read.
/// </summary>
public PdfRectangle Bounds { get; }
/// <summary>
/// The number of contours in this composite glyph. Should be less than zero.
/// </summary>
public short ContourCount { get; }
public PdfRectangle Bounds { get; set; }
public TemporaryCompositeLocation(long position, PdfRectangle bounds, short contourCount)
{
Position = position;
Bounds = bounds;
ContourCount = contourCount;
if (ContourCount >= 0 )
if (contourCount >= 0 )
{
throw new ArgumentException($"A composite glyph should not have a positive contour count. Got: {contourCount}.", nameof(contourCount));
}
Position = position;
Bounds = bounds;
}
}

View File

@@ -54,12 +54,12 @@
var glyph = GlyphTable.Glyphs[index];
if (glyph?.GlyphBounds == null)
if (glyph?.Bounds == null)
{
return false;
}
boundingBox = glyph.GlyphBounds;
boundingBox = glyph.Bounds;
return true;
}

View File

@@ -27,5 +27,18 @@
{
return new PdfMatrix3By2(r0c0, r0c1, r1c0, r1c1, x, y);
}
public PdfPoint ScaleAndRotate(PdfPoint source)
{
var newX = source.X * r0c0 + source.Y * r1c0;
var newY = source.X * r0c1 + source.Y * r1c1;
return new PdfPoint(newX, newY);
}
public PdfPoint Translate(PdfPoint source)
{
return new PdfPoint(source.X + r2c0, source.Y + r2c1);
}
}
}