From d4813ba3a95f540f3b584c497eeca43ae7cc3254 Mon Sep 17 00:00:00 2001 From: Eliot Jones Date: Sun, 2 Dec 2018 14:02:24 +0000 Subject: [PATCH] #20 add name table to truetype parsing so we can check the name of parsed system fonts --- .../Parser/TrueTypeFontParserTests.cs | 4 + .../TrueTypeMacintoshEncodingIdentifier.cs | 51 ++++++ .../TrueTypeMacintoshLanguageIdentifier.cs | 124 +++++++++++++++ .../TrueType/Names/TrueTypeNameRecord.cs | 30 ++++ .../Names/TrueTypePlatformIdentifier.cs | 25 +++ .../TrueTypeUnicodeEncodingIndentifier.cs | 37 +++++ .../TrueTypeWindowsEncodingIdentifier.cs | 53 +++++++ .../Fonts/TrueType/Parser/TableRegister.cs | 6 + .../TrueType/Parser/TrueTypeFontParser.cs | 16 +- .../Fonts/TrueType/Tables/NameTable.cs | 145 ++++++++++++++++-- .../Fonts/TrueType/TrueTypeFontProgram.cs | 5 + 11 files changed, 477 insertions(+), 19 deletions(-) create mode 100644 src/UglyToad.PdfPig/Fonts/TrueType/Names/TrueTypeMacintoshEncodingIdentifier.cs create mode 100644 src/UglyToad.PdfPig/Fonts/TrueType/Names/TrueTypeMacintoshLanguageIdentifier.cs create mode 100644 src/UglyToad.PdfPig/Fonts/TrueType/Names/TrueTypeNameRecord.cs create mode 100644 src/UglyToad.PdfPig/Fonts/TrueType/Names/TrueTypePlatformIdentifier.cs create mode 100644 src/UglyToad.PdfPig/Fonts/TrueType/Names/TrueTypeUnicodeEncodingIndentifier.cs create mode 100644 src/UglyToad.PdfPig/Fonts/TrueType/Names/TrueTypeWindowsEncodingIdentifier.cs diff --git a/src/UglyToad.PdfPig.Tests/Fonts/TrueType/Parser/TrueTypeFontParserTests.cs b/src/UglyToad.PdfPig.Tests/Fonts/TrueType/Parser/TrueTypeFontParserTests.cs index 409a7682..0465d9ae 100644 --- a/src/UglyToad.PdfPig.Tests/Fonts/TrueType/Parser/TrueTypeFontParserTests.cs +++ b/src/UglyToad.PdfPig.Tests/Fonts/TrueType/Parser/TrueTypeFontParserTests.cs @@ -152,6 +152,10 @@ var font = parser.Parse(input); Assert.NotNull(font.TableRegister.GlyphTable); + + var name = font.Name; + + Assert.Equal("Andada Regular", name); } [Fact] diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Names/TrueTypeMacintoshEncodingIdentifier.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Names/TrueTypeMacintoshEncodingIdentifier.cs new file mode 100644 index 00000000..25f456ee --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Names/TrueTypeMacintoshEncodingIdentifier.cs @@ -0,0 +1,51 @@ +namespace UglyToad.PdfPig.Fonts.TrueType.Names +{ + /// + /// The meaning of the platform specific encoding when the is . + /// + internal enum TrueTypeMacintoshEncodingIdentifier + { + /// + /// Roman. + /// + Roman = 0, + /// + /// Japanese. + /// + Japanese = 1, + /// + /// Traditional Chinese. + /// + 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 + } +} \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Names/TrueTypeMacintoshLanguageIdentifier.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Names/TrueTypeMacintoshLanguageIdentifier.cs new file mode 100644 index 00000000..8515ef4d --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Names/TrueTypeMacintoshLanguageIdentifier.cs @@ -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 + } +} diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Names/TrueTypeNameRecord.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Names/TrueTypeNameRecord.cs new file mode 100644 index 00000000..f96aa269 --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Names/TrueTypeNameRecord.cs @@ -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; + } + } +} diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Names/TrueTypePlatformIdentifier.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Names/TrueTypePlatformIdentifier.cs new file mode 100644 index 00000000..79d196a1 --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Names/TrueTypePlatformIdentifier.cs @@ -0,0 +1,25 @@ +namespace UglyToad.PdfPig.Fonts.TrueType.Names +{ + /// + /// The platform identifier for a TrueType/OpenType font allows for platform specific implementations. + /// + internal enum TrueTypePlatformIdentifier + { + /// + /// Unicode + /// + Unicode = 0, + /// + /// Macintosh. + /// + Macintosh = 1, + /// + /// The platform identifier 2 was originally to use with ISO 10646, but is now deprecated, as it and Unicode have identical character code assignments. + /// + Iso = 2, + /// + /// Microsoft Windows. + /// + Windows = 3 + } +} diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Names/TrueTypeUnicodeEncodingIndentifier.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Names/TrueTypeUnicodeEncodingIndentifier.cs new file mode 100644 index 00000000..09d6b2fb --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Names/TrueTypeUnicodeEncodingIndentifier.cs @@ -0,0 +1,37 @@ +namespace UglyToad.PdfPig.Fonts.TrueType.Names +{ + /// + /// The meaning of the platform specific encoding when the is . + /// + internal enum TrueTypeUnicodeEncodingIndentifier + { + /// + /// Default semantics. + /// + Default = 0, + /// + /// Version 1.1 semantics. + /// + Version1Point1 = 1, + /// + /// ISO 10646 1993 semantics (deprecated). + /// + Iso10646 = 2, + /// + /// Unicode 2.0 and above semantics for BMP characters only. + /// + Unicode2BmpOnly = 3, + /// + /// Uncidoe 2.0 and above semantics including non-BMP characters. + /// + Unicode2NonBmpAllowed = 4, + /// + /// Unicode Variation Sequences. + /// + UnicodeVariationSequences = 5, + /// + /// Full Unicode coverage. + /// + FullUnicode = 6 + } +} \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Names/TrueTypeWindowsEncodingIdentifier.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Names/TrueTypeWindowsEncodingIdentifier.cs new file mode 100644 index 00000000..872bfa45 --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Names/TrueTypeWindowsEncodingIdentifier.cs @@ -0,0 +1,53 @@ +namespace UglyToad.PdfPig.Fonts.TrueType.Names +{ + /// + /// The meaning of the platform specific encoding when the is . + /// + internal enum TrueTypeWindowsEncodingIdentifier + { + /// + /// Symbol. + /// + Symbol = 0, + /// + /// Unicode BMP. + /// + UnicodeBmp = 1, + /// + /// ShiftJIS. + /// + ShiftJis = 2, + /// + /// PRC. + /// + Prc = 3, + /// + /// Big5. + /// + Big5 = 4, + /// + /// Wansung. + /// + Wansung = 5, + /// + /// Johab. + /// + Johab = 6, + /// + /// Reserved. + /// + Reserved7 = 7, + /// + /// Reserved. + /// + Reserved8 = 8, + /// + /// Reserved. + /// + Reserved9 = 9, + /// + /// Unicode full repertoire. + /// + FullUnicode = 10 + } +} \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Parser/TableRegister.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Parser/TableRegister.cs index f1fe1f1c..9af03cbc 100644 --- a/src/UglyToad.PdfPig/Fonts/TrueType/Parser/TableRegister.cs +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Parser/TableRegister.cs @@ -45,6 +45,9 @@ [NotNull] public BasicMaximumProfileTable MaximumProfileTable { get; } + [CanBeNull] + public NameTable NameTable { get; } + public PostScriptTable PostScriptTable { get; } /// @@ -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); diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Parser/TrueTypeFontParser.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Parser/TrueTypeFontParser.cs index 5716f39c..5eb60110 100644 --- a/src/UglyToad.PdfPig/Fonts/TrueType/Parser/TrueTypeFontParser.cs +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Parser/TrueTypeFontParser.cs @@ -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 diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/NameTable.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/NameTable.cs index 4b8f7bb3..093dcce2 100644 --- a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/NameTable.cs +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/NameTable.cs @@ -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 NameRecords { get; } + + public NameTable(TrueTypeHeaderTable directoryTable, + string fontName, + string fontFamilyName, + string fontSubFamilyName, + IReadOnlyList 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(), diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/TrueTypeFontProgram.cs b/src/UglyToad.PdfPig/Fonts/TrueType/TrueTypeFontProgram.cs index f320e700..bd6e40f8 100644 --- a/src/UglyToad.PdfPig/Fonts/TrueType/TrueTypeFontProgram.cs +++ b/src/UglyToad.PdfPig/Fonts/TrueType/TrueTypeFontProgram.cs @@ -5,6 +5,7 @@ using CidFonts; using Geometry; using Parser; + using Util.JetBrains.Annotations; internal class TrueTypeFontProgram : ICidFontProgram { @@ -12,8 +13,12 @@ public IReadOnlyDictionary TableHeaders { get; } + [NotNull] public TableRegister TableRegister { get; } + [CanBeNull] + public string Name => TableRegister.NameTable?.FontName; + public TrueTypeFontProgram(decimal version, IReadOnlyDictionary tableHeaders, TableRegister tableRegister) { Version = version;