#20 add name table to truetype parsing so we can check the name of parsed system fonts

This commit is contained in:
Eliot Jones
2018-12-02 14:02:24 +00:00
parent 89bd649ced
commit d4813ba3a9
11 changed files with 477 additions and 19 deletions

View File

@@ -152,6 +152,10 @@
var font = parser.Parse(input);
Assert.NotNull(font.TableRegister.GlyphTable);
var name = font.Name;
Assert.Equal("Andada Regular", name);
}
[Fact]

View File

@@ -0,0 +1,51 @@
namespace UglyToad.PdfPig.Fonts.TrueType.Names
{
/// <summary>
/// The meaning of the platform specific encoding when the <see cref="TrueTypePlatformIdentifier"/> is <see cref="TrueTypePlatformIdentifier.Macintosh"/>.
/// </summary>
internal enum TrueTypeMacintoshEncodingIdentifier
{
/// <summary>
/// Roman.
/// </summary>
Roman = 0,
/// <summary>
/// Japanese.
/// </summary>
Japanese = 1,
/// <summary>
/// Traditional Chinese.
/// </summary>
ChineseTraditional = 2,
Korean = 3,
Arabic = 5,
Hebrew = 5,
Greek = 6,
Russian = 7,
RSymbol = 8,
Devanagari = 9,
Gurmukhi = 10,
Gujarati = 11,
Oriya = 12,
Bengali = 13,
Tamil = 14,
Telugu = 15,
Kannada = 16,
Malayalam = 17,
Sinhalese = 18,
Burmese = 19,
Khmer = 20,
Thai = 21,
Laotian = 22,
Georgian = 23,
Armenian = 24,
ChineseSimplified = 25,
Tibetan = 26,
Mongolian = 27,
Geez = 28,
Slavic = 29,
Vietnamese = 30,
Sindhi = 31,
Uninterpreted = 32
}
}

View File

@@ -0,0 +1,124 @@
namespace UglyToad.PdfPig.Fonts.TrueType.Names
{
internal enum TrueTypeMacintoshLanguageIdentifier
{
English = 0,
French = 1,
German = 2,
Italian = 3,
Dutch = 4,
Swedish = 5,
Spanish = 6,
Danish = 7,
Portuguese = 8,
Norwegian = 9,
Hebrew = 10,
Japanese = 11,
Arabic = 12,
Finnish = 13,
Greek = 14,
Icelandic = 15,
Maltese = 16,
Turkish = 17,
Croatian = 18,
ChineseTraditional = 19,
Urdu = 20,
Hindi = 21,
Thai = 22,
Korean = 23,
Lithuanian = 24,
Polish = 25,
Hungarian = 26,
Estonian = 27,
Latvian = 28,
Sami = 29,
Faroese = 30,
FarsiPersian = 31,
Russian = 32,
ChineseSimplified = 33,
Flemish = 34,
IrishGaelic = 35,
Albanian = 36,
Romanian = 37,
Czech = 38,
Slovak = 39,
Slovenian = 40,
Yiddish = 41,
Serbian = 42,
Macedonian = 43,
Bulgarian = 44,
Ukrainian = 45,
Byelorussian = 46,
Uzbek = 47,
Kazakh = 48,
AzerbaijaniCyrillic = 49,
AzerbaijaniArabic = 50,
Armenian = 51,
Georgian = 52,
Moldavian = 53,
Kirghiz = 54,
Tajiki = 55,
Turkmen = 56,
Mongolian = 57,
MongolianCyrillic = 58,
Pashto = 59,
Kurdish = 60,
Kashmiri = 61,
Sindhi = 62,
Tibetan = 63,
Nepali = 64,
Sanskrit = 65,
Marathi = 66,
Bengali = 67,
Assamese = 68,
Gujarati = 69,
Punjabi = 70,
Oriya = 71,
Malayalam = 72,
Kannada = 73,
Tamil = 74,
Telugu = 75,
Sinhalese = 76,
Burmese = 77,
Khmer = 78,
Lao = 79,
Vietnamese = 80,
Indonesian = 81,
Tagalog = 82,
MalayRoman = 83,
MalayArabic = 84,
Amharic = 85,
Tigrinya = 86,
Galla = 87,
Somali = 88,
Swahili = 89,
KinyarwandaRuanda = 90,
Rundi = 91,
NyanjaChewa = 92,
Malagasy = 93,
Esperanto = 94,
Welsh = 128,
Basque = 129,
Catalan = 130,
Latin = 131,
Quechua = 132,
Guarani = 133,
Aymara = 134,
Tatar = 135,
Uighur = 136,
Dzongkha = 137,
JavaneseRoman = 138,
SundaneseRoman = 139,
Galician = 140,
Afrikaans = 141,
Breton = 142,
Inuktitut = 143,
ScottishGaelic = 144,
ManxGaelic = 145,
IrishGaelicWithDotAbove = 146,
Tongan = 147,
GreekPolytonic = 148,
Greenlandic = 149,
AzerbaijaniRoman = 150
}
}

View File

@@ -0,0 +1,30 @@
namespace UglyToad.PdfPig.Fonts.TrueType.Names
{
internal class TrueTypeNameRecord
{
public TrueTypePlatformIdentifier PlatformId { get; }
public int PlatformEncodingId { get; }
public int LanguageId { get; }
public int NameId { get; }
public int Length { get; }
public int Offset { get; }
public string Value { get; }
public TrueTypeNameRecord(TrueTypePlatformIdentifier platformId, int platformEncodingId, int languageId, int nameId, int length, int offset, string value)
{
PlatformId = platformId;
PlatformEncodingId = platformEncodingId;
LanguageId = languageId;
NameId = nameId;
Length = length;
Offset = offset;
Value = value;
}
}
}

View File

@@ -0,0 +1,25 @@
namespace UglyToad.PdfPig.Fonts.TrueType.Names
{
/// <summary>
/// The platform identifier for a TrueType/OpenType font allows for platform specific implementations.
/// </summary>
internal enum TrueTypePlatformIdentifier
{
/// <summary>
/// Unicode
/// </summary>
Unicode = 0,
/// <summary>
/// Macintosh.
/// </summary>
Macintosh = 1,
/// <summary>
/// The platform identifier 2 was originally to use with ISO 10646, but is now deprecated, as it and Unicode have identical character code assignments.
/// </summary>
Iso = 2,
/// <summary>
/// Microsoft Windows.
/// </summary>
Windows = 3
}
}

View File

@@ -0,0 +1,37 @@
namespace UglyToad.PdfPig.Fonts.TrueType.Names
{
/// <summary>
/// The meaning of the platform specific encoding when the <see cref="TrueTypePlatformIdentifier"/> is <see cref="TrueTypePlatformIdentifier.Unicode"/>.
/// </summary>
internal enum TrueTypeUnicodeEncodingIndentifier
{
/// <summary>
/// Default semantics.
/// </summary>
Default = 0,
/// <summary>
/// Version 1.1 semantics.
/// </summary>
Version1Point1 = 1,
/// <summary>
/// ISO 10646 1993 semantics (deprecated).
/// </summary>
Iso10646 = 2,
/// <summary>
/// Unicode 2.0 and above semantics for BMP characters only.
/// </summary>
Unicode2BmpOnly = 3,
/// <summary>
/// Uncidoe 2.0 and above semantics including non-BMP characters.
/// </summary>
Unicode2NonBmpAllowed = 4,
/// <summary>
/// Unicode Variation Sequences.
/// </summary>
UnicodeVariationSequences = 5,
/// <summary>
/// Full Unicode coverage.
/// </summary>
FullUnicode = 6
}
}

View File

@@ -0,0 +1,53 @@
namespace UglyToad.PdfPig.Fonts.TrueType.Names
{
/// <summary>
/// The meaning of the platform specific encoding when the <see cref="TrueTypePlatformIdentifier"/> is <see cref="TrueTypePlatformIdentifier.Windows"/>.
/// </summary>
internal enum TrueTypeWindowsEncodingIdentifier
{
/// <summary>
/// Symbol.
/// </summary>
Symbol = 0,
/// <summary>
/// Unicode BMP.
/// </summary>
UnicodeBmp = 1,
/// <summary>
/// ShiftJIS.
/// </summary>
ShiftJis = 2,
/// <summary>
/// PRC.
/// </summary>
Prc = 3,
/// <summary>
/// Big5.
/// </summary>
Big5 = 4,
/// <summary>
/// Wansung.
/// </summary>
Wansung = 5,
/// <summary>
/// Johab.
/// </summary>
Johab = 6,
/// <summary>
/// Reserved.
/// </summary>
Reserved7 = 7,
/// <summary>
/// Reserved.
/// </summary>
Reserved8 = 8,
/// <summary>
/// Reserved.
/// </summary>
Reserved9 = 9,
/// <summary>
/// Unicode full repertoire.
/// </summary>
FullUnicode = 10
}
}

View File

@@ -45,6 +45,9 @@
[NotNull]
public BasicMaximumProfileTable MaximumProfileTable { get; }
[CanBeNull]
public NameTable NameTable { get; }
public PostScriptTable PostScriptTable { get; }
/// <summary>
@@ -73,6 +76,7 @@
HorizontalMetricsTable = builder.HorizontalMetricsTable;
IndexToLocationTable = builder.IndexToLocationTable ?? throw new ArgumentException("The builder did not contain the index to location table.");
MaximumProfileTable = builder.MaximumProfileTable ?? throw new ArgumentException("The builder did not contain the maximum profile table.");
NameTable = builder.NameTable;
PostScriptTable = builder.PostScriptTable;
CMapTable = builder.CMapTable;
KerningTable = builder.KerningTable;
@@ -106,6 +110,8 @@
public KerningTable KerningTable { get; set; }
public NameTable NameTable { get; set; }
public TableRegister Build()
{
return new TableRegister(this);

View File

@@ -2,6 +2,7 @@
{
using System;
using System.Collections.Generic;
using Exceptions;
using Tables;
using Util.JetBrains.Annotations;
@@ -61,7 +62,7 @@
if (!tables.TryGetValue(TrueTypeHeaderTable.Head, out var table))
{
throw new InvalidOperationException($"The {TrueTypeHeaderTable.Head} table is required.");
throw new InvalidFontFormatException($"The {TrueTypeHeaderTable.Head} table is required.");
}
// head
@@ -69,7 +70,7 @@
if (!tables.TryGetValue(TrueTypeHeaderTable.Hhea, out var hHead))
{
throw new InvalidOperationException("The horizontal header table is required.");
throw new InvalidFontFormatException("The horizontal header table is required.");
}
// hhea
@@ -77,7 +78,7 @@
if (!tables.TryGetValue(TrueTypeHeaderTable.Maxp, out var maxHeaderTable))
{
throw new InvalidOperationException("The maximum profile table is required.");
throw new InvalidFontFormatException("The maximum profile table is required.");
}
// maxp
@@ -88,12 +89,17 @@
{
builder.PostScriptTable = PostScriptTable.Load(data, postscriptHeaderTable, builder.MaximumProfileTable);
}
if (tables.TryGetValue(TrueTypeHeaderTable.Name, out var nameTable))
{
builder.NameTable = NameTable.Load(data, nameTable);
}
if (!isPostScript)
{
if (!tables.TryGetValue(TrueTypeHeaderTable.Loca, out var indexToLocationHeaderTable))
{
throw new InvalidOperationException("The location to index table is required for non-PostScript fonts.");
throw new InvalidFontFormatException("The location to index table is required for non-PostScript fonts.");
}
// loca
@@ -102,7 +108,7 @@
if (!tables.TryGetValue(TrueTypeHeaderTable.Glyf, out var glyphHeaderTable))
{
throw new InvalidOperationException("The glyph table is required for non-PostScript fonts.");
throw new InvalidFontFormatException("The glyph table is required for non-PostScript fonts.");
}
// glyf

View File

@@ -1,53 +1,170 @@
namespace UglyToad.PdfPig.Fonts.TrueType.Tables
{
using System.Collections.Generic;
using System.Text;
using Names;
using Util;
using Util.JetBrains.Annotations;
internal class NameTable
{
public string Tag => TrueTypeHeaderTable.Name;
public TrueTypeHeaderTable DirectoryTable { get; }
public static void Load(TrueTypeDataBytes data, TrueTypeHeaderTable table)
public string FontName { get; }
public string FontFamilyName { get; }
public string FontSubFamilyName { get; }
public IReadOnlyList<TrueTypeNameRecord> NameRecords { get; }
public NameTable(TrueTypeHeaderTable directoryTable,
string fontName,
string fontFamilyName,
string fontSubFamilyName,
IReadOnlyList<TrueTypeNameRecord> nameRecords)
{
DirectoryTable = directoryTable;
FontName = fontName;
FontFamilyName = fontFamilyName;
FontSubFamilyName = fontSubFamilyName;
NameRecords = nameRecords;
}
internal static NameTable Load(TrueTypeDataBytes data, TrueTypeHeaderTable table)
{
data.Seek(table.Offset);
// ReSharper disable once UnusedVariable
var format = data.ReadUnsignedShort();
var count = data.ReadUnsignedShort();
var stringOffset = data.ReadUnsignedShort();
var names = new NameRecord[count];
var names = new NameRecordBuilder[count];
for (var i = 0; i < count; i++)
{
names[i] = NameRecord.Read(data);
names[i] = NameRecordBuilder.Read(data);
}
var strings = new TrueTypeNameRecord[count];
for (var i = 0; i < count; i++)
{
var nameRecord = names[i];
var encoding = OtherEncodings.Iso88591;
switch (nameRecord.PlatformId)
{
case TrueTypePlatformIdentifier.Windows:
{
var platformEncoding = (TrueTypeWindowsEncodingIdentifier) nameRecord.PlatformEncodingId;
if (platformEncoding == TrueTypeWindowsEncodingIdentifier.Symbol
|| platformEncoding == TrueTypeWindowsEncodingIdentifier.UnicodeBmp)
{
encoding = Encoding.BigEndianUnicode;
}
break;
}
case TrueTypePlatformIdentifier.Unicode:
{
encoding = Encoding.BigEndianUnicode;
break;
}
case TrueTypePlatformIdentifier.Iso:
{
switch (nameRecord.PlatformEncodingId)
{
case 0:
encoding = Encoding.GetEncoding("US-ASCII");
break;
case 1:
encoding = Encoding.GetEncoding("ISO-10646-UCS-2");
break;
}
break;
}
}
var position = table.Offset + stringOffset + nameRecord.Offset;
data.Seek(position);
var str = data.ReadString(nameRecord.Length, encoding);
strings[i] = nameRecord.ToNameRecord(str);
}
return new NameTable(table, GetName(4, strings), GetName(1, strings), GetName(2, strings), strings);
}
public struct NameRecord
[CanBeNull]
private static string GetName(int nameId, TrueTypeNameRecord[] names)
{
public int PlatformId { get; }
const int windowsEnUs = 409;
string windows = null;
string any = null;
public int PlatformSpecificId { get; }
for (var i = 0; i < names.Length; i++)
{
var name = names[i];
public int LanguageId { get; }
if (name.NameId != nameId)
{
continue;
}
public int NameId { get; }
if (name.PlatformId == TrueTypePlatformIdentifier.Windows && name.LanguageId == windowsEnUs)
{
return name.Value;
}
if (name.PlatformId == TrueTypePlatformIdentifier.Windows)
{
windows = name.Value;
}
any = name.Value;
}
return windows ?? any;
}
private struct NameRecordBuilder
{
public TrueTypePlatformIdentifier PlatformId { get; }
public int PlatformEncodingId { get; }
private int LanguageId { get; }
private int NameId { get; }
public int Length { get; }
public int Offset { get; }
public NameRecord(int platformId, int platformSpecificId, int languageId, int nameId, int length, int offset)
private NameRecordBuilder(int platformId, int platformEncodingId, int languageId, int nameId, int length, int offset)
{
PlatformId = platformId;
PlatformSpecificId = platformSpecificId;
PlatformId = (TrueTypePlatformIdentifier)platformId;
PlatformEncodingId = platformEncodingId;
LanguageId = languageId;
NameId = nameId;
Length = length;
Offset = offset;
}
public static NameRecord Read(TrueTypeDataBytes data)
public TrueTypeNameRecord ToNameRecord(string s)
{
return new NameRecord(data.ReadUnsignedShort(),
return new TrueTypeNameRecord(PlatformId, PlatformEncodingId,
LanguageId, NameId, Length, Offset, s);
}
public static NameRecordBuilder Read(TrueTypeDataBytes data)
{
return new NameRecordBuilder(data.ReadUnsignedShort(),
data.ReadUnsignedShort(),
data.ReadUnsignedShort(),
data.ReadUnsignedShort(),

View File

@@ -5,6 +5,7 @@
using CidFonts;
using Geometry;
using Parser;
using Util.JetBrains.Annotations;
internal class TrueTypeFontProgram : ICidFontProgram
{
@@ -12,8 +13,12 @@
public IReadOnlyDictionary<string, TrueTypeHeaderTable> TableHeaders { get; }
[NotNull]
public TableRegister TableRegister { get; }
[CanBeNull]
public string Name => TableRegister.NameTable?.FontName;
public TrueTypeFontProgram(decimal version, IReadOnlyDictionary<string, TrueTypeHeaderTable> tableHeaders, TableRegister tableRegister)
{
Version = version;