From f4db97a998d5a75ae99a8be53d9b0a566f58b000 Mon Sep 17 00:00:00 2001 From: Eliot Jones Date: Sun, 29 Apr 2018 15:51:27 +0100 Subject: [PATCH] implement full kerning table loader for true type font --- .../Fonts/TrueType/Tables/Kerning/KernPair.cs | 33 +++++ .../Tables/Kerning/KerningSubTable.cs | 20 +++ .../Fonts/TrueType/Tables/KerningTable.cs | 114 +++++++++++++++++- 3 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 src/UglyToad.PdfPig/Fonts/TrueType/Tables/Kerning/KernPair.cs create mode 100644 src/UglyToad.PdfPig/Fonts/TrueType/Tables/Kerning/KerningSubTable.cs diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/Kerning/KernPair.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/Kerning/KernPair.cs new file mode 100644 index 00000000..dd778b65 --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/Kerning/KernPair.cs @@ -0,0 +1,33 @@ +namespace UglyToad.PdfPig.Fonts.TrueType.Tables.Kerning +{ + internal struct KernPair + { + /// + /// The index of the left-hand glyph. + /// + public int LeftGlyphIndex { get; } + + /// + /// The index of the right-hand glyph. + /// + public int RightGlyphIndex { get; } + + /// + /// The kerning value. For values greater than zero the characters are moved apart. + /// For values less than zero the characters are moved closer together. + /// + 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}."; + } + } +} diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/Kerning/KerningSubTable.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/Kerning/KerningSubTable.cs new file mode 100644 index 00000000..bdb80198 --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/Kerning/KerningSubTable.cs @@ -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 Pairs { get; } + + public KerningSubTable(int version, KernCoverage coverage, IReadOnlyList pairs) + { + Version = version; + Coverage = coverage; + Pairs = pairs; + } + } +} diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/KerningTable.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/KerningTable.cs index be76d077..ed5773a1 100644 --- a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/KerningTable.cs +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/KerningTable.cs @@ -1,9 +1,27 @@ namespace UglyToad.PdfPig.Fonts.TrueType.Tables { + using System.Collections.Generic; using Kerning; internal class KerningTable { + public IReadOnlyList KerningTables { get; } + + public KerningTable(IReadOnlyList kerningTables) + { + var notNull = new List(); + + 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(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); } } }