start adding other tables for reading true type fonts

This commit is contained in:
Eliot Jones
2018-03-03 16:25:32 +00:00
parent 174d9906fb
commit bd6427f26f
11 changed files with 352 additions and 11 deletions

View File

@@ -0,0 +1,26 @@
namespace UglyToad.PdfPig.Fonts.TrueType.Parser
{
using System;
using System.Collections.Generic;
using Tables;
/// <summary>
/// Holds tables while parsing a TrueType font
/// </summary>
internal class TableRegister
{
public GlyphDataTable GlyphDataTable { get; set; }
public HeaderTable HeaderTable { get; set; }
public HorizontalHeaderTable HorizontalHeaderTable { get; set; }
public HorizontalMetricsTable HorizontalMetricsTable { get; set; }
public IndexToLocationTable IndexToLocationTable { get; set; }
public BasicMaximumProfileTable MaximumProfileTable { get; set; }
public PostScriptTable PostScriptTable { get; set; }
}
}

View File

@@ -53,31 +53,37 @@
{
var isPostScript = tables.ContainsKey(TrueTypeHeaderTable.Cff);
var tableRegister = new TableRegister();
if (!tables.TryGetValue(TrueTypeHeaderTable.Head, out var table))
{
throw new InvalidOperationException($"The {TrueTypeHeaderTable.Head} table is required.");
}
var header = HeaderTable.Load(data, table);
// head
tableRegister.HeaderTable = HeaderTable.Load(data, table);
if (!tables.TryGetValue(TrueTypeHeaderTable.Hhea, out var hHead))
{
throw new InvalidOperationException("The horizontal header table is required.");
}
var horizontalHeader = HorizontalHeaderTable.Load(data, hHead);
// hhea
tableRegister.HorizontalHeaderTable = HorizontalHeaderTable.Load(data, hHead);
if (!tables.TryGetValue(TrueTypeHeaderTable.Maxp, out var maxHeaderTable))
{
throw new InvalidOperationException("The maximum profile table is required.");
}
var maximumProfile = BasicMaximumProfileTable.Load(data, maxHeaderTable);
// maxp
tableRegister.MaximumProfileTable = BasicMaximumProfileTable.Load(data, maxHeaderTable);
// post
var postScriptTable = default(PostScriptTable);
if (tables.TryGetValue(TrueTypeHeaderTable.Post, out var postscriptHeaderTable))
{
postScriptTable = PostScriptTable.Load(data, table, maximumProfile);
tableRegister.PostScriptTable = PostScriptTable.Load(data, table, tableRegister.MaximumProfileTable);
}
if (!isPostScript)
@@ -87,18 +93,43 @@
throw new InvalidOperationException("The location to index table is required for non-PostScript fonts.");
}
var indexToLocationTable =
IndexToLocationTable.Load(data, indexToLocationHeaderTable, header, maximumProfile);
// loca
tableRegister.IndexToLocationTable =
IndexToLocationTable.Load(data, indexToLocationHeaderTable, tableRegister);
if (!tables.TryGetValue(TrueTypeHeaderTable.Glyf, out var glyphHeaderTable))
{
throw new InvalidOperationException("The glpyh table is required for non-PostScript fonts.");
}
var glyphTable = GlyphDataTable.Load(data, glyphHeaderTable, header, indexToLocationTable);
// glyf
tableRegister.GlyphDataTable = GlyphDataTable.Load(data, glyphHeaderTable, tableRegister);
OptionallyParseTables(tables, data, tableRegister);
}
return new TrueTypeFont(version, tables, header);
return new TrueTypeFont(version, tables, tableRegister.HeaderTable);
}
private static void OptionallyParseTables(IReadOnlyDictionary<string, TrueTypeHeaderTable> tables, TrueTypeDataBytes data, TableRegister tableRegister)
{
// cmap
// hmtx
if (tables.TryGetValue(TrueTypeHeaderTable.Hmtx, out var hmtxHeaderTable))
{
tableRegister.HorizontalMetricsTable = HorizontalMetricsTable.Load(data, hmtxHeaderTable, tableRegister);
}
// name
if (tables.TryGetValue(TrueTypeHeaderTable.Name, out var nameHeaderTable))
{
// TODO: Not important
}
// os2
// kern
}
}
}

View File

@@ -0,0 +1,15 @@
namespace UglyToad.PdfPig.Fonts.TrueType.Tables.CMapSubTables
{
internal class ByteEncodingCMapTable : ICMapSubTable
{
public static ByteEncodingCMapTable Load(TrueTypeDataBytes data)
{
var length = data.ReadUnsignedShort();
var version = data.ReadUnsignedShort();
var glyphMapping = data.ReadByteArray(256);
return new ByteEncodingCMapTable();
}
}
}

View File

@@ -0,0 +1,100 @@
namespace UglyToad.PdfPig.Fonts.TrueType.Tables.CMapSubTables
{
using System;
using System.Collections.Generic;
internal class HighByteMappingCMapTable : ICMapSubTable
{
public static HighByteMappingCMapTable Load(TrueTypeDataBytes data, int numberOfGlyphs)
{
var length = data.ReadUnsignedShort();
var version = data.ReadUnsignedShort();
var subHeaderKeys = new int[256];
var maximumSubHeaderIndex = 0;
for (var i = 0; i < 256; i++)
{
var value = data.ReadUnsignedShort();
maximumSubHeaderIndex = Math.Max(maximumSubHeaderIndex, value / 8);
subHeaderKeys[i] = value;
}
var subHeaderCount = maximumSubHeaderIndex + 1;
var subHeaders = new SubHeader[subHeaderCount];
for (var i = 0; i < subHeaderCount; i++)
{
var firstCode = data.ReadUnsignedShort();
var entryCount = data.ReadUnsignedShort();
var idDelta = data.ReadSignedShort();
var idRangeOffset = data.ReadUnsignedShort() - (subHeaderCount - i - 1) * 8 - 2;
subHeaders[i] = new SubHeader(firstCode, entryCount, idDelta, idRangeOffset);
}
var glyphIndexArrayOffset = data.Position;
var characterCodeToGlyphId = new Dictionary<int, int>();
for (var i = 0; i < subHeaderCount; i++)
{
var subHeader = subHeaders[i];
data.Seek(glyphIndexArrayOffset + subHeader.IdRangeOffset);
for (int j = 0; j < subHeader.EntryCount; j++)
{
int characterCode = (i << 8) + (subHeader.FirstCode + j);
var p = data.ReadUnsignedShort();
if (p > 0)
{
p = (p + subHeader.IdDelta) % 65536;
}
if (p >= numberOfGlyphs)
{
continue;
}
characterCodeToGlyphId[characterCode] = p;
}
}
return new HighByteMappingCMapTable();
}
public struct SubHeader
{
/// <summary>
/// First valid low byte for the sub header.
/// </summary>
public int FirstCode { get; }
/// <summary>
/// Number of valid low bytes for the sub header.
/// </summary>
public int EntryCount { get; }
/// <summary>
/// Adds to the value from the sub array to provide the glyph index.
/// </summary>
public short IdDelta { get; }
/// <summary>
/// The number of bytes past the actual location of this value where the glyph index array element starts.
/// </summary>
public int IdRangeOffset { get; }
public SubHeader(int firstCode, int entryCount, short idDelta, int idRangeOffset)
{
FirstCode = firstCode;
EntryCount = entryCount;
IdDelta = idDelta;
IdRangeOffset = idRangeOffset;
}
}
}
}

View File

@@ -0,0 +1,7 @@
namespace UglyToad.PdfPig.Fonts.TrueType.Tables.CMapSubTables
{
internal interface ICMapSubTable
{
}
}

View File

@@ -0,0 +1,10 @@
namespace UglyToad.PdfPig.Fonts.TrueType.Tables.CMapSubTables
{
internal class SegmentMappingDeltaValuesCMapTable : ICMapSubTable
{
public static SegmentMappingDeltaValuesCMapTable Load(TrueTypeDataBytes data)
{
return null;
}
}
}

View File

@@ -0,0 +1,93 @@
namespace UglyToad.PdfPig.Fonts.TrueType.Tables
{
using Parser;
using System.Collections.Generic;
using CMapSubTables;
internal class CMapTable : ITable
{
private readonly IReadOnlyList<ICMapSubTable> subTables;
public string Tag => TrueTypeHeaderTable.Cmap;
public int Version { get; }
public TrueTypeHeaderTable DirectoryTable { get; }
public CMapTable(int version, TrueTypeHeaderTable directoryTable, IReadOnlyList<ICMapSubTable> subTables)
{
this.subTables = subTables;
Version = version;
DirectoryTable = directoryTable;
}
public static CMapTable Load(TrueTypeDataBytes data, TrueTypeHeaderTable table, TableRegister tableRegister)
{
data.Seek(table.Offset);
var tableVersionNumber = data.ReadUnsignedShort();
var numberOfEncodingTables = data.ReadUnsignedShort();
var subTableHeaders = new SubTableHeaderEntry[numberOfEncodingTables];
for (int i = 0; i < numberOfEncodingTables; i++)
{
var platformId = data.ReadUnsignedShort();
var encodingId = data.ReadUnsignedShort();
var offset = data.ReadUnsignedInt();
subTableHeaders[i] = new SubTableHeaderEntry(platformId, encodingId, offset);
}
var tables = new List<ICMapSubTable>(numberOfEncodingTables);
var numberofGlyphs = tableRegister.MaximumProfileTable.NumberOfGlyphs;
for (var i = 0; i < subTableHeaders.Length; i++)
{
var header = subTableHeaders[i];
data.Seek(table.Offset + header.Offset);
var format = data.ReadUnsignedShort();
switch (format)
{
case 0:
{
// Simple 1 to 1 mapping of character codes to glyph codes.
var item = ByteEncodingCMapTable.Load(data);
tables.Add(item);
break;
}
case 1:
{
// Useful for CJK characters. Use mixed 8/16 bit encoding.
var item = HighByteMappingCMapTable.Load(data, numberofGlyphs);
tables.Add(item);
break;
}
}
}
return new CMapTable(tableVersionNumber, table, tables);
}
private class SubTableHeaderEntry
{
public int PlatformId { get; }
public int EncodingId { get; }
public long Offset { get; }
public SubTableHeaderEntry(int platformId, int encodingId, long offset)
{
PlatformId = platformId;
EncodingId = encodingId;
Offset = offset;
}
}
}
}

View File

@@ -2,6 +2,7 @@
{
using System;
using System.Collections.Generic;
using Parser;
using Util.JetBrains.Annotations;
/// <summary>
@@ -22,11 +23,13 @@
Glyphs = glyphs ?? throw new ArgumentNullException(nameof(glyphs));
}
public static GlyphDataTable Load(TrueTypeDataBytes data, TrueTypeHeaderTable table, HeaderTable headerTable,
IndexToLocationTable indexToLocationTable)
public static GlyphDataTable Load(TrueTypeDataBytes data, TrueTypeHeaderTable table, TableRegister tableRegister)
{
data.Seek(table.Offset);
var headerTable = tableRegister.HeaderTable;
var indexToLocationTable = tableRegister.IndexToLocationTable;
var offsets = indexToLocationTable.GlyphOffsets;
var entryCount = offsets.Length;

View File

@@ -0,0 +1,50 @@
namespace UglyToad.PdfPig.Fonts.TrueType.Tables
{
using Parser;
internal class HorizontalMetricsTable : ITable
{
private readonly int[] advancedWidths;
private readonly short[] leftSideBearings;
public string Tag => TrueTypeHeaderTable.Hmtx;
public TrueTypeHeaderTable DirectoryTable { get; }
public HorizontalMetricsTable(TrueTypeHeaderTable directoryTable, int[] advancedWidths, short[] leftSideBearings)
{
this.advancedWidths = advancedWidths;
this.leftSideBearings = leftSideBearings;
DirectoryTable = directoryTable;
}
public static HorizontalMetricsTable Load(TrueTypeDataBytes data, TrueTypeHeaderTable table, TableRegister tableRegister)
{
var metricCount = tableRegister.HorizontalHeaderTable.NumberOfHeaderMetrics;
var glyphCount = tableRegister.MaximumProfileTable.NumberOfGlyphs;
data.Seek(table.Offset);
// The number of entries in the left side bearing field per entry is number of glyphs - number of metrics
var additionalLeftSideBearingLength = glyphCount - metricCount;
var advancedWidths = new int[metricCount];
// For bearings over the metric count, the width is the same as the last width in advanced widths.
var leftSideBearings = new short[glyphCount];
for (var i = 0; i < metricCount; i++)
{
advancedWidths[i] = data.ReadUnsignedShort();
leftSideBearings[i] = data.ReadSignedShort();
}
for (var i = 0; i < additionalLeftSideBearingLength; i++)
{
leftSideBearings[metricCount + i] = data.ReadSignedShort();
}
return new HorizontalMetricsTable(table, advancedWidths, leftSideBearings);
}
}
}

View File

@@ -1,6 +1,7 @@
namespace UglyToad.PdfPig.Fonts.TrueType.Tables
{
using System;
using Parser;
/// <summary>
/// Stores the offset to the glyph locations relative to the start of the glyph data table.
@@ -24,13 +25,16 @@
GlyphOffsets = glyphOffsets;
}
public static IndexToLocationTable Load(TrueTypeDataBytes data, TrueTypeHeaderTable table, HeaderTable headerTable, BasicMaximumProfileTable maximumProfileTable)
public static IndexToLocationTable Load(TrueTypeDataBytes data, TrueTypeHeaderTable table, TableRegister tableRegister)
{
const short shortFormat = 0;
const short longFormat = 1;
data.Seek(table.Offset);
var headerTable = tableRegister.HeaderTable;
var maximumProfileTable = tableRegister.MaximumProfileTable;
var format = headerTable.IndexToLocFormat;
var glyphCount = maximumProfileTable.NumberOfGlyphs + 1;

View File

@@ -15,6 +15,8 @@
this.inputBytes = inputBytes;
}
public long Position => inputBytes.CurrentOffset;
public float Read32Fixed()
{
float retval = ReadSignedShort();