From 92c0ef14cbd7565cd242b7e6882c161b3e39f7c6 Mon Sep 17 00:00:00 2001 From: Eliot Jones Date: Sat, 31 Mar 2018 12:11:12 +0100 Subject: [PATCH] support format 6 cmap sub tables for truetype fonts. pass the truetypefont to the ifont implementation so we can use it to access font data --- .../Parser/TrueTypeFontParserTests.cs | 2 +- .../Parser/Handlers/TrueTypeFontHandler.cs | 2 +- .../Fonts/Simple/TrueTypeSimpleFont.cs | 11 +++- .../TrueType/Parser/TrueTypeFontParser.cs | 7 ++- .../Tables/CMapSubTables/Format4CMapTable.cs | 5 +- .../Tables/CMapSubTables/ICMapSubTable.cs | 5 ++ .../TrimmedTableMappingCMapTable.cs | 60 +++++++++++++++++++ .../Fonts/TrueType/Tables/CMapTable.cs | 4 +- .../Fonts/TrueType/TrueTypeFont.cs | 19 ++++-- 9 files changed, 100 insertions(+), 15 deletions(-) create mode 100644 src/UglyToad.PdfPig/Fonts/TrueType/Tables/CMapSubTables/TrimmedTableMappingCMapTable.cs diff --git a/src/UglyToad.PdfPig.Tests/Fonts/TrueType/Parser/TrueTypeFontParserTests.cs b/src/UglyToad.PdfPig.Tests/Fonts/TrueType/Parser/TrueTypeFontParserTests.cs index e1955057..2b223f2e 100644 --- a/src/UglyToad.PdfPig.Tests/Fonts/TrueType/Parser/TrueTypeFontParserTests.cs +++ b/src/UglyToad.PdfPig.Tests/Fonts/TrueType/Parser/TrueTypeFontParserTests.cs @@ -122,7 +122,7 @@ name = "cvt "; } - var match = font.Tables[name]; + var match = font.TableHeaders[name]; var offset = long.Parse(parts[1]); var length = long.Parse(parts[2]); diff --git a/src/UglyToad.PdfPig/Fonts/Parser/Handlers/TrueTypeFontHandler.cs b/src/UglyToad.PdfPig/Fonts/Parser/Handlers/TrueTypeFontHandler.cs index bdce7c4c..ec16712a 100644 --- a/src/UglyToad.PdfPig/Fonts/Parser/Handlers/TrueTypeFontHandler.cs +++ b/src/UglyToad.PdfPig/Fonts/Parser/Handlers/TrueTypeFontHandler.cs @@ -70,7 +70,7 @@ Encoding encoding = encodingReader.Read(dictionary, isLenientParsing, descriptor); - return new TrueTypeSimpleFont(name, firstCharacter, lastCharacter, widths, descriptor, toUnicodeCMap, encoding); + return new TrueTypeSimpleFont(name, firstCharacter, lastCharacter, widths, descriptor, toUnicodeCMap, encoding, font); } private TrueTypeFont ParseTrueTypeFont(FontDescriptor descriptor) diff --git a/src/UglyToad.PdfPig/Fonts/Simple/TrueTypeSimpleFont.cs b/src/UglyToad.PdfPig/Fonts/Simple/TrueTypeSimpleFont.cs index 28f09f2a..a049d70c 100644 --- a/src/UglyToad.PdfPig/Fonts/Simple/TrueTypeSimpleFont.cs +++ b/src/UglyToad.PdfPig/Fonts/Simple/TrueTypeSimpleFont.cs @@ -7,6 +7,7 @@ using Geometry; using IO; using Tokenization.Tokens; + using TrueType; using Util.JetBrains.Annotations; internal class TrueTypeSimpleFont : IFont @@ -19,6 +20,8 @@ private readonly FontDescriptor descriptor; [CanBeNull] private readonly Encoding encoding; + [CanBeNull] + private readonly TrueTypeFont font; public NameToken Name { get; } @@ -27,16 +30,18 @@ [NotNull] public ToUnicodeCMap ToUnicode { get; set; } - public TrueTypeSimpleFont(NameToken name, int firstCharacterCode, int lastCharacterCode, decimal[] widths, + public TrueTypeSimpleFont(NameToken name, int firstCharacterCode, int lastCharacterCode, decimal[] widths, FontDescriptor descriptor, - [CanBeNull]CMap toUnicodeCMap, - [CanBeNull]Encoding encoding) + [CanBeNull] CMap toUnicodeCMap, + [CanBeNull] Encoding encoding, + [CanBeNull]TrueTypeFont font) { this.firstCharacterCode = firstCharacterCode; this.lastCharacterCode = lastCharacterCode; this.widths = widths; this.descriptor = descriptor; this.encoding = encoding; + this.font = font; Name = name; IsVertical = false; diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Parser/TrueTypeFontParser.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Parser/TrueTypeFontParser.cs index 81d1b076..ede11ee6 100644 --- a/src/UglyToad.PdfPig/Fonts/TrueType/Parser/TrueTypeFontParser.cs +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Parser/TrueTypeFontParser.cs @@ -11,9 +11,13 @@ { var version = (decimal)data.Read32Fixed(); int numberOfTables = data.ReadUnsignedShort(); + + // Read these data points to move to the correct data location. + // ReSharper disable UnusedVariable int searchRange = data.ReadUnsignedShort(); int entrySelector = data.ReadUnsignedShort(); int rangeShift = data.ReadUnsignedShort(); + // ReSharper restore UnusedVariable var tables = new Dictionary(); @@ -80,7 +84,6 @@ tableRegister.MaximumProfileTable = BasicMaximumProfileTable.Load(data, maxHeaderTable); // post - var postScriptTable = default(PostScriptTable); if (tables.TryGetValue(TrueTypeHeaderTable.Post, out var postscriptHeaderTable)) { tableRegister.PostScriptTable = PostScriptTable.Load(data, table, tableRegister.MaximumProfileTable); @@ -108,7 +111,7 @@ OptionallyParseTables(tables, data, tableRegister); } - return new TrueTypeFont(version, tables, tableRegister.HeaderTable); + return new TrueTypeFont(version, tables, tableRegister); } private static void OptionallyParseTables(IReadOnlyDictionary tables, TrueTypeDataBytes data, TableRegister tableRegister) diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/CMapSubTables/Format4CMapTable.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/CMapSubTables/Format4CMapTable.cs index 633f3c98..6e14e5e3 100644 --- a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/CMapSubTables/Format4CMapTable.cs +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/CMapSubTables/Format4CMapTable.cs @@ -69,10 +69,11 @@ namespace UglyToad.PdfPig.Fonts.TrueType.Tables.CMapSubTables var idRangeOffsets = data.ReadUnsignedShortArray(segmentCount); - const int singleIntsRead = 16; + const int singleIntsRead = 8; const int intArraysRead = 8; - var remainingBytes = length - (singleIntsRead + intArraysRead * segmentCount); + // ReSharper disable once ArrangeRedundantParentheses + var remainingBytes = length - ((singleIntsRead * 2) + intArraysRead * segmentCount); var remainingInts = remainingBytes / 2; diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/CMapSubTables/ICMapSubTable.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/CMapSubTables/ICMapSubTable.cs index f1add3d7..3b48f289 100644 --- a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/CMapSubTables/ICMapSubTable.cs +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/CMapSubTables/ICMapSubTable.cs @@ -23,6 +23,11 @@ /// int EncodingId { get; } + /// + /// Maps from a character code to the array index of the glyph in the font data. + /// + /// The character code. + /// The index of the glyph information for this character. int CharacterCodeToGlyphIndex(int characterCode); } } \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/CMapSubTables/TrimmedTableMappingCMapTable.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/CMapSubTables/TrimmedTableMappingCMapTable.cs new file mode 100644 index 00000000..c0b46ad1 --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/CMapSubTables/TrimmedTableMappingCMapTable.cs @@ -0,0 +1,60 @@ +// ReSharper disable UnusedVariable +namespace UglyToad.PdfPig.Fonts.TrueType.Tables.CMapSubTables +{ + using System; + + /// + /// + /// A format 6 CMap sub-table which uses 2 bytes to map a contiguous range of character codes to glyph indices. + /// + internal class TrimmedTableMappingCMapTable : ICMapSubTable + { + private readonly int firstCharacterCode; + private readonly int entryCount; + private readonly int[] glyphIndices; + + public int PlatformId { get; } + public int EncodingId { get; } + + /// + /// Create a new . + /// + public TrimmedTableMappingCMapTable(int platformId, int encodingId, int firstCharacterCode, int entryCount, int[] glyphIndices) + { + this.firstCharacterCode = firstCharacterCode; + this.entryCount = entryCount; + this.glyphIndices = glyphIndices ?? throw new ArgumentNullException(nameof(glyphIndices)); + + PlatformId = platformId; + EncodingId = encodingId; + } + + public int CharacterCodeToGlyphIndex(int characterCode) + { + if (characterCode < firstCharacterCode || characterCode > firstCharacterCode + entryCount) + { + return 0; + } + + var offset = characterCode - firstCharacterCode; + + return glyphIndices[offset]; + } + + public static TrimmedTableMappingCMapTable Load(TrueTypeDataBytes data, int platformId, int encodingId) + { + var length = data.ReadUnsignedShort(); + var language = data.ReadUnsignedShort(); + + // First character code in the range. + var firstCode = data.ReadUnsignedShort(); + + // Number of character codes in the range. + var entryCount = data.ReadUnsignedShort(); + + var glyphIndices = data.ReadUnsignedShortArray(entryCount); + + return new TrimmedTableMappingCMapTable(platformId, encodingId, firstCode, entryCount, glyphIndices); + } + } +} diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/CMapTable.cs b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/CMapTable.cs index 11a5825f..c5362848 100644 --- a/src/UglyToad.PdfPig/Fonts/TrueType/Tables/CMapTable.cs +++ b/src/UglyToad.PdfPig/Fonts/TrueType/Tables/CMapTable.cs @@ -88,12 +88,12 @@ // Microsoft's standard mapping table. var item = Format4CMapTable.Load(data, header.PlatformId, header.EncodingId); tables.Add(item); - break; } case 6: { - // TODO: support format 6 for modern fonts. + var item = TrimmedTableMappingCMapTable.Load(data, header.PlatformId, header.EncodingId); + tables.Add(item); break; } } diff --git a/src/UglyToad.PdfPig/Fonts/TrueType/TrueTypeFont.cs b/src/UglyToad.PdfPig/Fonts/TrueType/TrueTypeFont.cs index d6b93353..69b9efd7 100644 --- a/src/UglyToad.PdfPig/Fonts/TrueType/TrueTypeFont.cs +++ b/src/UglyToad.PdfPig/Fonts/TrueType/TrueTypeFont.cs @@ -1,22 +1,33 @@ namespace UglyToad.PdfPig.Fonts.TrueType { + using System; using System.Collections.Generic; using CidFonts; + using Parser; using Tables; internal class TrueTypeFont : ICidFontProgram { public decimal Version { get; } - public IReadOnlyDictionary Tables { get; } + public IReadOnlyDictionary TableHeaders { get; } public HeaderTable HeaderTable { get; } + public CMapTable CMapTable { get; } + public GlyphDataTable GlyphTable { get; } - public TrueTypeFont(decimal version, IReadOnlyDictionary tables, HeaderTable headerTable) + public TrueTypeFont(decimal version, IReadOnlyDictionary tableHeaders, TableRegister tableRegister) { + if (tableRegister == null) + { + throw new ArgumentNullException(nameof(tableRegister)); + } + Version = version; - Tables = tables; - HeaderTable = headerTable; + TableHeaders = tableHeaders; + HeaderTable = tableRegister.HeaderTable; + CMapTable = tableRegister.CMapTable; + GlyphTable = tableRegister.GlyphDataTable; } } } \ No newline at end of file