mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-10-14 19:05:01 +08:00
start adding other tables for reading true type fonts
This commit is contained in:
26
src/UglyToad.PdfPig/Fonts/TrueType/Parser/TableRegister.cs
Normal file
26
src/UglyToad.PdfPig/Fonts/TrueType/Parser/TableRegister.cs
Normal 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; }
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
namespace UglyToad.PdfPig.Fonts.TrueType.Tables.CMapSubTables
|
||||
{
|
||||
internal interface ICMapSubTable
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
namespace UglyToad.PdfPig.Fonts.TrueType.Tables.CMapSubTables
|
||||
{
|
||||
internal class SegmentMappingDeltaValuesCMapTable : ICMapSubTable
|
||||
{
|
||||
public static SegmentMappingDeltaValuesCMapTable Load(TrueTypeDataBytes data)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
93
src/UglyToad.PdfPig/Fonts/TrueType/Tables/CMapTable.cs
Normal file
93
src/UglyToad.PdfPig/Fonts/TrueType/Tables/CMapTable.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
|
@@ -15,6 +15,8 @@
|
||||
this.inputBytes = inputBytes;
|
||||
}
|
||||
|
||||
public long Position => inputBytes.CurrentOffset;
|
||||
|
||||
public float Read32Fixed()
|
||||
{
|
||||
float retval = ReadSignedShort();
|
||||
|
Reference in New Issue
Block a user