implement full kerning table loader for true type font

This commit is contained in:
Eliot Jones
2018-04-29 15:51:27 +01:00
parent 2bf4922c47
commit f4db97a998
3 changed files with 166 additions and 1 deletions

View File

@@ -0,0 +1,33 @@
namespace UglyToad.PdfPig.Fonts.TrueType.Tables.Kerning
{
internal struct KernPair
{
/// <summary>
/// The index of the left-hand glyph.
/// </summary>
public int LeftGlyphIndex { get; }
/// <summary>
/// The index of the right-hand glyph.
/// </summary>
public int RightGlyphIndex { get; }
/// <summary>
/// The kerning value. For values greater than zero the characters are moved apart.
/// For values less than zero the characters are moved closer together.
/// </summary>
public short Value { get; }
public KernPair(int leftGlyphIndex, int rightGlyphIndex, short value)
{
LeftGlyphIndex = leftGlyphIndex;
RightGlyphIndex = rightGlyphIndex;
Value = value;
}
public override string ToString()
{
return $"Left: {LeftGlyphIndex}, Right: {RightGlyphIndex}, Value {Value}.";
}
}
}

View File

@@ -0,0 +1,20 @@
namespace UglyToad.PdfPig.Fonts.TrueType.Tables.Kerning
{
using System.Collections.Generic;
internal class KerningSubTable
{
public int Version { get; }
public KernCoverage Coverage { get; }
public IReadOnlyList<KernPair> Pairs { get; }
public KerningSubTable(int version, KernCoverage coverage, IReadOnlyList<KernPair> pairs)
{
Version = version;
Coverage = coverage;
Pairs = pairs;
}
}
}

View File

@@ -1,9 +1,27 @@
namespace UglyToad.PdfPig.Fonts.TrueType.Tables
{
using System.Collections.Generic;
using Kerning;
internal class KerningTable
{
public IReadOnlyList<KerningSubTable> KerningTables { get; }
public KerningTable(IReadOnlyList<KerningSubTable> kerningTables)
{
var notNull = new List<KerningSubTable>();
foreach (var kerningTable in kerningTables)
{
if (kerningTable != null)
{
notNull.Add(kerningTable);
}
}
KerningTables = notNull;
}
public static KerningTable Load(TrueTypeDataBytes data, TrueTypeHeaderTable headerTable)
{
data.Seek(headerTable.Offset);
@@ -12,17 +30,111 @@
var numberOfSubtables = data.ReadUnsignedShort();
var subTables = new KerningSubTable[numberOfSubtables];
for (var i = 0; i < numberOfSubtables; i++)
{
var currentOffset = data.Position;
var subtableVersion = data.ReadUnsignedShort();
var subtableLength = data.ReadUnsignedShort();
var coverage = data.ReadUnsignedShort();
var kernCoverage = (KernCoverage) coverage;
var format = ((coverage & 255) >> 8);
switch (format)
{
case 0:
subTables[i] = ReadFormat0Table(subtableVersion, data, kernCoverage);
break;
case 2:
subTables[i] = ReadFormat2Table(subtableVersion, data, kernCoverage, currentOffset);
break;
}
}
return new KerningTable();
return new KerningTable(subTables);
}
private static KerningSubTable ReadFormat0Table(int version, TrueTypeDataBytes data, KernCoverage coverage)
{
var numberOfPairs = data.ReadUnsignedShort();
// ReSharper disable once UnusedVariable
var searchRange = data.ReadUnsignedShort();
// ReSharper disable once UnusedVariable
var entrySelector = data.ReadUnsignedShort();
// ReSharper disable once UnusedVariable
var rangeShift = data.ReadUnsignedShort();
var pairs = new KernPair[numberOfPairs];
for (int i = 0; i < numberOfPairs; i++)
{
var leftGlyphIndex = data.ReadUnsignedShort();
var rightGlyphIndex = data.ReadUnsignedShort();
var value = data.ReadSignedShort();
pairs[i] = new KernPair(leftGlyphIndex, rightGlyphIndex, value);
}
return new KerningSubTable(version, coverage, pairs);
}
private static KerningSubTable ReadFormat2Table(int version, TrueTypeDataBytes data, KernCoverage coverage, long tableStartOffset)
{
// TODO: Implement and test this;
return null;
var rowWidth = data.ReadUnsignedShort();
var leftClassTableOffset = data.ReadUnsignedShort();
var rightClassTableOffset = data.ReadUnsignedShort();
var kerningArrayOffset = data.ReadUnsignedShort();
data.Seek(tableStartOffset + leftClassTableOffset);
var leftTableFirstGlyph = data.ReadUnsignedShort();
var numberOfLeftGlyphs = data.ReadUnsignedShort();
var leftGlyphClassValues = new int[numberOfLeftGlyphs];
for (var i = 0; i < numberOfLeftGlyphs; i++)
{
leftGlyphClassValues[i] = data.ReadUnsignedShort();
}
data.Seek(tableStartOffset + rightClassTableOffset);
var rightTableFirstGlyph = data.ReadUnsignedShort();
var numberOfRightGlyphs = data.ReadUnsignedShort();
var rightGlyphClassValues = new int[numberOfRightGlyphs];
for (var i = 0; i < numberOfRightGlyphs; i++)
{
rightGlyphClassValues[i] = data.ReadUnsignedShort();
}
data.Seek(tableStartOffset + kerningArrayOffset);
var pairs = new List<KernPair>(numberOfRightGlyphs * numberOfLeftGlyphs);
// Data is a [left glyph count, right glyph count] array:
for (int i = 0; i < numberOfLeftGlyphs; i++)
{
var leftClassValue = leftGlyphClassValues[i];
for (int j = 0; j < numberOfRightGlyphs; j++)
{
var rightClassValue = rightGlyphClassValues[j];
pairs.Add(new KernPair(leftClassValue, rightClassValue, data.ReadSignedShort()));
}
}
return new KerningSubTable(version, coverage, pairs);
}
}
}