Ejemplo n.º 1
0
        private static GlyphRecord[] GetGlyphRecordsInFont(TrueTypeFont font, TrueTypeDataBytes data)
        {
            var indexToLocationTable = font.TableRegister.IndexToLocationTable;

            var numGlyphs = indexToLocationTable.GlyphOffsets.Count - 1;

            var glyphDirectory = font.TableRegister.GlyphTable.DirectoryTable;

            data.Seek(glyphDirectory.Offset);

            var glyphRecords = new GlyphRecord[numGlyphs];

            for (var i = 0; i < numGlyphs; i++)
            {
                var glyphOffset = (int)(glyphDirectory.Offset + indexToLocationTable.GlyphOffsets[i]);

                if (indexToLocationTable.GlyphOffsets[i + 1] <= indexToLocationTable.GlyphOffsets[i])
                {
                    glyphRecords[i] = new GlyphRecord(glyphOffset);
                    continue;
                }

                data.Seek(glyphOffset);

                if (glyphOffset >= glyphDirectory.Offset + glyphDirectory.Length)
                {
                    throw new InvalidOperationException($"Failed to read expected number of glyphs {numGlyphs}, only got to index {i} before reaching end of input.");
                }

                var numberOfContours = data.ReadSignedShort();
                var type             = numberOfContours >= 0 ? GlyphType.Simple : GlyphType.Composite;

                // Read bounds.
                data.ReadSignedShort();
                data.ReadSignedShort();
                data.ReadSignedShort();
                data.ReadSignedShort();

                if (type == GlyphType.Simple)
                {
                    ReadSimpleGlyph(data, numberOfContours);
                    glyphRecords[i] = new GlyphRecord(glyphOffset, type, (int)(data.Position - glyphOffset));
                }
                else
                {
                    var glyphIndices = ReadCompositeGlyph(data);

                    // Skip hinting instructions but include them in the output.
                    var next = indexToLocationTable.GlyphOffsets[i + 1];
                    data.Seek(glyphDirectory.Offset + next - 1);

                    glyphRecords[i] = new GlyphRecord(glyphOffset, type, (int)(data.Position - glyphOffset), glyphIndices);
                }
            }

            return(glyphRecords);
        }
Ejemplo n.º 2
0
        private static KerningSubTable ReadFormat2Table(int version, TrueTypeDataBytes data, KernCoverage coverage, long tableStartOffset)
        {
            // TODO: Implement and test this;
            return(null);

#pragma warning disable 162
            var rowWidth = data.ReadUnsignedShort();

            var leftClassTableOffset  = data.ReadUnsignedShort();
            var rightClassTableOffset = data.ReadUnsignedShort();

            var kerningArrayOffset = data.ReadUnsignedShort();

            data.Seek(tableStartOffset + leftClassTableOffset);

            var leftTableFirstGlyph = data.ReadUnsignedShort();
            var numberOfLeftGlyphs  = data.ReadUnsignedShort();

            var leftGlyphClassValues = new int[numberOfLeftGlyphs];

            for (var i = 0; i < numberOfLeftGlyphs; i++)
            {
                leftGlyphClassValues[i] = data.ReadUnsignedShort();
            }

            data.Seek(tableStartOffset + rightClassTableOffset);

            var rightTableFirstGlyph = data.ReadUnsignedShort();
            var numberOfRightGlyphs  = data.ReadUnsignedShort();

            var rightGlyphClassValues = new int[numberOfRightGlyphs];

            for (var i = 0; i < numberOfRightGlyphs; i++)
            {
                rightGlyphClassValues[i] = data.ReadUnsignedShort();
            }

            data.Seek(tableStartOffset + kerningArrayOffset);

            var pairs = new List <KernPair>(numberOfRightGlyphs * numberOfLeftGlyphs);

            // Data is a [left glyph count, right glyph count] array:
            for (int i = 0; i < numberOfLeftGlyphs; i++)
            {
                var leftClassValue = leftGlyphClassValues[i];
                for (int j = 0; j < numberOfRightGlyphs; j++)
                {
                    var rightClassValue = rightGlyphClassValues[j];

                    pairs.Add(new KernPair(leftClassValue, rightClassValue, data.ReadSignedShort()));
                }
            }

            return(new KerningSubTable(version, coverage, pairs));

#pragma warning restore 162
        }
Ejemplo n.º 3
0
        public static GlyphDataTable Load(TrueTypeDataBytes data, TrueTypeHeaderTable table, TableRegister.Builder tableRegister)
        {
            data.Seek(table.Offset);

            var indexToLocationTable = tableRegister.IndexToLocationTable;

            var offsets = indexToLocationTable.GlyphOffsets;

            var entryCount = offsets.Length;

            var glyphCount = entryCount - 1;

            var glyphs = new IGlyphDescription[glyphCount];

            var emptyGlyph = Glyph.Empty(tableRegister.HeaderTable.Bounds);

            var compositeLocations = new Dictionary <int, TemporaryCompositeLocation>();

            for (var i = 0; i < glyphCount; i++)
            {
                if (offsets[i] == offsets[i + 1])
                {
                    // empty glyph
                    glyphs[i] = emptyGlyph;
                    continue;
                }

                data.Seek(offsets[i] + table.Offset);

                var contourCount = data.ReadSignedShort();

                var minX = data.ReadSignedShort();
                var minY = data.ReadSignedShort();
                var maxX = data.ReadSignedShort();
                var maxY = data.ReadSignedShort();

                var bounds = new PdfRectangle(minX, minY, maxX, maxY);

                // If the number of contours is greater than or equal zero it's a simple glyph.
                if (contourCount >= 0)
                {
                    glyphs[i] = ReadSimpleGlyph(data, contourCount, bounds);
                }
                else
                {
                    compositeLocations.Add(i, new TemporaryCompositeLocation(data.Position, bounds, contourCount));
                }
            }

            // Build composite glyphs by combining simple and other composite glyphs.
            foreach (var compositeLocation in compositeLocations)
            {
                glyphs[compositeLocation.Key] = ReadCompositeGlyph(data, compositeLocation.Value, compositeLocations, glyphs, emptyGlyph);
            }

            return(new GlyphDataTable(table, glyphs));
        }
Ejemplo n.º 4
0
        public static BasicMaximumProfileTable Load(TrueTypeDataBytes data, TrueTypeHeaderTable table)
        {
            data.Seek(table.Offset);

            var version        = data.Read32Fixed();
            var numberOfGlyphs = data.ReadUnsignedShort();

            if (Math.Abs(version - 0.5) < float.Epsilon)
            {
                return(new BasicMaximumProfileTable(table, version, numberOfGlyphs));
            }

            var maxPoints            = data.ReadUnsignedShort();
            var maxContours          = data.ReadUnsignedShort();
            var maxCompositePoints   = data.ReadUnsignedShort();
            var maxCompositeContours = data.ReadUnsignedShort();

            var maxZones              = data.ReadUnsignedShort();
            var maxTwilightPoints     = data.ReadUnsignedShort();
            var maxStorage            = data.ReadUnsignedShort();
            var maxFunctionDefs       = data.ReadUnsignedShort();
            var maxInstructionDefs    = data.ReadUnsignedShort();
            var maxStackElements      = data.ReadUnsignedShort();
            var maxSizeOfInstructions = data.ReadUnsignedShort();
            var maxComponentElements  = data.ReadUnsignedShort();
            var maxComponentDepth     = data.ReadUnsignedShort();

            return(new MaximumProfileTable(table, version, numberOfGlyphs, maxPoints,
                                           maxContours, maxCompositePoints, maxCompositeContours, maxZones,
                                           maxTwilightPoints, maxStorage, maxFunctionDefs, maxInstructionDefs,
                                           maxStackElements, maxSizeOfInstructions, maxComponentElements,
                                           maxComponentDepth));
        }
Ejemplo n.º 5
0
        public static HorizontalMetricsTable Load(TrueTypeDataBytes data, TrueTypeHeaderTable table, TableRegister.Builder tableRegister)
        {
            var glyphCount  = tableRegister.MaximumProfileTable.NumberOfGlyphs;
            var metricCount = tableRegister.HorizontalHeaderTable.NumberOfHeaderMetrics;

            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, metricCount));
        }
Ejemplo n.º 6
0
        public static KerningTable Load(TrueTypeDataBytes data, TrueTypeHeaderTable headerTable)
        {
            data.Seek(headerTable.Offset);

            var version = data.ReadUnsignedShort();

            var numberOfSubtables = data.ReadUnsignedShort();

            var subTables = new KerningSubTable[numberOfSubtables];

            for (var i = 0; i < numberOfSubtables; i++)
            {
                var currentOffset = data.Position;

                var subtableVersion = data.ReadUnsignedShort();
                var subtableLength  = data.ReadUnsignedShort();
                var coverage        = data.ReadUnsignedShort();

                var kernCoverage = (KernCoverage)coverage;
                var format       = ((coverage & 255) >> 8);

                switch (format)
                {
                case 0:
                    subTables[i] = ReadFormat0Table(subtableVersion, data, kernCoverage);
                    break;

                case 2:
                    subTables[i] = ReadFormat2Table(subtableVersion, data, kernCoverage, currentOffset);
                    break;
                }
            }

            return(new KerningTable(subTables));
        }
Ejemplo n.º 7
0
        /// <summary>
        /// Load the index to location (loca) table from the TrueType font. Requires the maximum profile (maxp) and header (head) table
        /// to have been parsed.
        /// </summary>
        internal static IndexToLocationTable Load(TrueTypeDataBytes data, TrueTypeHeaderTable table, TableRegister.Builder tableRegister)
        {
            if (data == null)
            {
                throw new ArgumentNullException(nameof(data));
            }

            if (tableRegister == null)
            {
                throw new ArgumentNullException(nameof(tableRegister));
            }

            data.Seek(table.Offset);

            var headerTable         = tableRegister.HeaderTable;
            var maximumProfileTable = tableRegister.MaximumProfileTable;

            if (headerTable == null)
            {
                throw new InvalidFontFormatException("No header (head) table was defined in this font.");
            }

            if (maximumProfileTable == null)
            {
                throw new InvalidFontFormatException("No maximum profile (maxp) table was defined in this font.");
            }

            var format = (EntryFormat)headerTable.IndexToLocFormat;

            var glyphCount = maximumProfileTable.NumberOfGlyphs + 1;

            uint[] offsets;

            switch (format)
            {
            case EntryFormat.Short:
            {
                // The local offset divided by 2 is stored.
                offsets = new uint[glyphCount];
                for (var i = 0; i < glyphCount; i++)
                {
                    offsets[i] = (uint)(data.ReadUnsignedShort() * 2);
                }
                break;
            }

            case EntryFormat.Long:
            {
                // The actual offset is stored.
                offsets = data.ReadUnsignedIntArray(glyphCount);
                break;
            }

            default:
                throw new InvalidOperationException($"The format {format} was invalid for the index to location (loca) table.");
            }


            return(new IndexToLocationTable(table, format, offsets));
        }
Ejemplo n.º 8
0
        public NameTable Parse(TrueTypeHeaderTable header, TrueTypeDataBytes data, TableRegister.Builder register)
        {
            data.Seek(header.Offset);
            // ReSharper disable once UnusedVariable
            var format       = data.ReadUnsignedShort();
            var count        = data.ReadUnsignedShort();
            var stringOffset = data.ReadUnsignedShort();

            var names = new NameRecordBuilder[count];

            for (var i = 0; i < count; i++)
            {
                names[i] = NameRecordBuilder.Read(data);
            }

            var strings = new TrueTypeNameRecord[count];
            var offset  = header.Offset + stringOffset;

            for (var i = 0; i < count; i++)
            {
                strings[i] = GetTrueTypeNameRecord(names[i], data, offset);
            }

            return(new NameTable(header, GetName(4, strings), GetName(1, strings), GetName(2, strings), strings));
        }
Ejemplo n.º 9
0
        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;

            var glyphCount = entryCount - 1;

            var glyphs = new IGlyphDescription[glyphCount];

            for (var i = 0; i < glyphCount; i++)
            {
                if (offsets[i] == offsets[i + 1])
                {
                    // empty glyph
                    continue;
                }

                data.Seek(offsets[i] + table.Offset);

                var contourCount = data.ReadSignedShort();

                var minX = data.ReadSignedShort();
                var minY = data.ReadSignedShort();
                var maxX = data.ReadSignedShort();
                var maxY = data.ReadSignedShort();

                var bounds = new PdfRectangle(minX, minY, maxX, maxY);

                // If the number of contours is greater than or equal zero it's a simple glyph.
                if (contourCount >= 0)
                {
                    glyphs[i] = ReadSimpleGlyph(data, contourCount, bounds);
                }
                else
                {
                }
            }

            return(new GlyphDataTable(table, glyphs));
        }
Ejemplo n.º 10
0
        /// <summary>
        /// Read the header table from the data stream.
        /// </summary>
        public static HeaderTable Load(TrueTypeDataBytes data, TrueTypeHeaderTable table)
        {
            data.Seek(table.Offset);
            var version            = data.Read32Fixed();
            var fontRevision       = data.Read32Fixed();
            var checkSumAdjustment = data.ReadUnsignedInt();
            var magicNumber        = data.ReadUnsignedInt();

            if (magicNumber != 0x5F0F3CF5)
            {
                throw new InvalidOperationException("The magic number for this TrueType font was incorrect. Value was: " + magicNumber);
            }

            var flags      = data.ReadUnsignedShort();
            var unitsPerEm = data.ReadUnsignedShort();

            if (unitsPerEm < 16 || unitsPerEm > 16384)
            {
                throw new InvalidOperationException($"The units per em for this TrueType font was incorrect, value should be between 16 and 16384 but found {unitsPerEm} istead.");
            }

            DateTime created;

            try
            {
                created = data.ReadInternationalDate();
            }
            catch (InvalidFontFormatException)
            {
                created = DateTime.MinValue;
            }

            DateTime modified;

            try
            {
                modified = data.ReadInternationalDate();
            }
            catch (InvalidFontFormatException)
            {
                modified = DateTime.MinValue;
            }

            var xMin              = data.ReadSignedShort();
            var yMin              = data.ReadSignedShort();
            var xMax              = data.ReadSignedShort();
            var yMax              = data.ReadSignedShort();
            var macStyle          = data.ReadUnsignedShort();
            var lowestRecPpem     = data.ReadUnsignedShort();
            var fontDirectionHint = data.ReadSignedShort();
            var indexToLocFormat  = (IndexToLocationTable.EntryFormat)data.ReadSignedShort();
            var glyphDataFormat   = data.ReadSignedShort();

            return(new HeaderTable(table, version, fontRevision, checkSumAdjustment,
                                   magicNumber, flags, unitsPerEm, created, modified,
                                   xMin, yMin, xMax, yMax, macStyle, lowestRecPpem,
                                   fontDirectionHint, indexToLocFormat, glyphDataFormat));
        }
Ejemplo n.º 11
0
        private bool TryReadFile(string fileName, bool readNameFirst, string fontName, out TrueTypeFont font)
        {
            font = null;

            var bytes = File.ReadAllBytes(fileName);

            var data = new TrueTypeDataBytes(new ByteArrayInputBytes(bytes));

            if (readNameFirst)
            {
                var name = TrueTypeFontParser.GetNameTable(data);

                if (name == null)
                {
                    lock (readFilesLock)
                    {
                        readFiles.Add(fileName);
                    }

                    return(false);
                }

                var fontNameFromFile = name.GetPostscriptName() ?? name.FontName;

                nameToFileNameMap.TryAdd(fontNameFromFile, fileName);

                if (!string.Equals(fontNameFromFile, fontName, StringComparison.OrdinalIgnoreCase))
                {
                    lock (readFilesLock)
                    {
                        readFiles.Add(fileName);
                    }

                    return(false);
                }
            }

            data.Seek(0);
            font = TrueTypeFontParser.Parse(data);
            var psName = font.TableRegister.NameTable?.GetPostscriptName() ?? font.Name;

            lock (CacheLock)
            {
                if (!Cache.ContainsKey(psName))
                {
                    Cache[psName] = font;
                }
            }

            lock (readFilesLock)
            {
                readFiles.Add(fileName);
            }

            return(true);
        }
Ejemplo n.º 12
0
        public static GlyphDataTable Load(TrueTypeDataBytes data, TrueTypeHeaderTable table, TableRegister.Builder tableRegister)
        {
            data.Seek(table.Offset);

            var bytes = data.ReadByteArray((int)table.Length);

            return(new GlyphDataTable(table, tableRegister.IndexToLocationTable.GlyphOffsets,
                                      tableRegister.HeaderTable.Bounds,
                                      new TrueTypeDataBytes(bytes)));
        }
Ejemplo n.º 13
0
        public HorizontalMetricsTable Parse(TrueTypeHeaderTable header, TrueTypeDataBytes data, TableRegister.Builder register)
        {
            var glyphCount  = register.MaximumProfileTable.NumberOfGlyphs;
            var metricCount = register.HorizontalHeaderTable.NumberOfHeaderMetrics;

            data.Seek(header.Offset);
            var bytesRead = 0;


            var horizontalMetrics = new HorizontalMetric[metricCount];


            for (var i = 0; i < metricCount; i++)
            {
                var width = data.ReadUnsignedShort();
                var lsb   = data.ReadSignedShort();

                horizontalMetrics[i] = new HorizontalMetric(width, lsb);

                bytesRead += 4;
            }

            int numberNonHorizontal = glyphCount - metricCount;

            // handle bad fonts with too many hmetrics
            if (numberNonHorizontal < 0)
            {
                numberNonHorizontal = glyphCount;
            }

            // The number of entries in the left side bearing field per entry is number of glyphs - number of metrics
            // For bearings over the metric count, the width is the same as the last width in advanced widths.
            var additionalLeftSideBearings = new short[numberNonHorizontal];

            for (var i = 0; i < additionalLeftSideBearings.Length; i++)
            {
                if (bytesRead >= header.Length)
                {
                    break;
                }

                additionalLeftSideBearings[i] = data.ReadSignedShort();
                bytesRead += 2;
            }

            return(new HorizontalMetricsTable(header, horizontalMetrics, additionalLeftSideBearings));
        }
Ejemplo n.º 14
0
        public HorizontalHeaderTable Parse(TrueTypeHeaderTable header, TrueTypeDataBytes data, TableRegister.Builder register)
        {
            data.Seek(header.Offset);
            var majorVersion = data.ReadUnsignedShort();
            var minorVersion = data.ReadUnsignedShort();

            var ascender  = data.ReadSignedShort();
            var descender = data.ReadSignedShort();
            var lineGap   = data.ReadSignedShort();

            var advancedWidthMax = data.ReadUnsignedShort();

            var minLeftSideBearing  = data.ReadSignedShort();
            var minRightSideBearing = data.ReadSignedShort();
            var xMaxExtent          = data.ReadSignedShort();

            var caretSlopeRise = data.ReadSignedShort();
            var caretSlopeRun  = data.ReadSignedShort();
            var caretOffset    = data.ReadSignedShort();

            // Reserved section
            data.ReadSignedShort();
            data.ReadSignedShort();
            data.ReadSignedShort();
            data.ReadSignedShort();

            var metricDataFormat = data.ReadSignedShort();

            if (metricDataFormat != 0)
            {
                throw new NotSupportedException("The metric data format for a horizontal header table should be 0.");
            }

            var numberOfHeaderMetrics = data.ReadUnsignedShort();

            return(new HorizontalHeaderTable(header, majorVersion, minorVersion, ascender,
                                             descender, lineGap, advancedWidthMax,
                                             minLeftSideBearing,
                                             minRightSideBearing,
                                             xMaxExtent,
                                             caretSlopeRise,
                                             caretSlopeRun,
                                             caretOffset,
                                             metricDataFormat,
                                             numberOfHeaderMetrics));
        }
Ejemplo n.º 15
0
        public static T Parse <T>(TrueTypeHeaderTable table, TrueTypeDataBytes data, TableRegister.Builder register) where T : ITrueTypeTable
        {
            //checksum https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6.html
            uint sum    = 0;
            var  nLongs = (table.Length + 3) / 4;

            data.Seek(table.Offset);
            while (nLongs-- > 0)
            {
                sum += (uint)data.ReadSignedInt();
            }

            if (sum != table.CheckSum)
            {
                Trace.TraceWarning("Table with invalid checksum found in TrueType font file.");
            }

            if (typeof(T) == typeof(CMapTable))
            {
                return((T)(object)CMapTableParser.Parse(table, data, register));
            }

            if (typeof(T) == typeof(HorizontalMetricsTable))
            {
                return((T)(object)HorizontalMetricsTableParser.Parse(table, data, register));
            }

            if (typeof(T) == typeof(NameTable))
            {
                return((T)(object)NameTableParser.Parse(table, data, register));
            }

            if (typeof(T) == typeof(Os2Table))
            {
                return((T)(object)Os2TableParser.Parse(table, data, register));
            }

            if (typeof(T) == typeof(HorizontalHeaderTable))
            {
                return((T)(object)HorizontalHeaderTableParser.Parse(table, data, register));
            }

            throw new NotImplementedException();
        }
Ejemplo n.º 16
0
        public static PostScriptTable Load(TrueTypeDataBytes data, TrueTypeHeaderTable table, BasicMaximumProfileTable maximumProfileTable)
        {
            data.Seek(table.Offset);
            var formatType         = data.Read32Fixed();
            var italicAngle        = data.Read32Fixed();
            var underlinePosition  = data.ReadSignedShort();
            var underlineThickness = data.ReadSignedShort();
            var isFixedPitch       = data.ReadUnsignedInt();
            var minMemType42       = data.ReadUnsignedInt();
            var maxMemType42       = data.ReadUnsignedInt();
            var mimMemType1        = data.ReadUnsignedInt();
            var maxMemType1        = data.ReadUnsignedInt();

            var glyphNames = GetGlyphNamesByFormat(data, maximumProfileTable, formatType);

            return(new PostScriptTable(table, (decimal)formatType, (decimal)italicAngle,
                                       underlinePosition, underlineThickness, isFixedPitch,
                                       minMemType42, maxMemType42, mimMemType1,
                                       maxMemType1, glyphNames));
        }
Ejemplo n.º 17
0
        public static IndexToLocationTable Load(TrueTypeDataBytes data, TrueTypeHeaderTable table, TableRegister.Builder 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;

            var offsets = new long[glyphCount];

            switch (format)
            {
            case shortFormat:
            {         // The local offset divided by 2 is stored.
                for (int i = 0; i < glyphCount; i++)
                {
                    offsets[i] = data.ReadUnsignedShort() * 2;
                }
                break;
            }

            case longFormat:
            {
                // The actual offset is stored.
                data.ReadUnsignedIntArray(offsets, glyphCount);
                break;
            }

            default:
                throw new InvalidOperationException($"The format {format} was invalid for the index to location (loca) table.");
            }


            return(new IndexToLocationTable(table, offsets));
        }
Ejemplo n.º 18
0
        private bool TryReadFile(string fileName, bool readNameFirst, string fontName, out TrueTypeFontProgram font)
        {
            font = null;
            readFiles.Add(fileName);

            using (var fileStream = File.OpenRead(fileName))
            {
                var input = new StreamInputBytes(fileStream);
                var data  = new TrueTypeDataBytes(input);

                if (readNameFirst)
                {
                    var name = trueTypeFontParser.GetNameTable(data);

                    if (name == null)
                    {
                        return(false);
                    }

                    var fontNameFromFile = name.GetPostscriptName() ?? name.FontName;

                    nameToFileNameMap[fontNameFromFile] = fileName;

                    if (!string.Equals(fontNameFromFile, fontName, StringComparison.OrdinalIgnoreCase))
                    {
                        return(false);
                    }
                }

                data.Seek(0);
                font = trueTypeFontParser.Parse(data);
                var psName = font.TableRegister.NameTable?.GetPostscriptName() ?? font.Name;
                if (!cache.ContainsKey(psName))
                {
                    cache[psName] = font;
                }

                return(true);
            }
        }
Ejemplo n.º 19
0
        public CMapTable Parse(TrueTypeHeaderTable header, TrueTypeDataBytes data, TableRegister.Builder register)
        {
            data.Seek(header.Offset);

            var tableVersionNumber = data.ReadUnsignedShort();

            var numberOfEncodingTables = data.ReadUnsignedShort();

            var subTableHeaders = new SubTableHeaderEntry[numberOfEncodingTables];

            for (int i = 0; i < numberOfEncodingTables; i++)
            {
                var platformId = (TrueTypeCMapPlatform)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 = register.MaximumProfileTable.NumberOfGlyphs;

            for (var i = 0; i < subTableHeaders.Length; i++)
            {
                var subTableHeader = subTableHeaders[i];

                data.Seek(header.Offset + subTableHeader.Offset);

                var format = data.ReadUnsignedShort();

                /*
                 * There are 9 currently available formats:
                 * 0: Character code and glyph indices are restricted to a single byte. Rare.
                 * 2: Suitable for CJK characters. Contain mixed 8/16 byte encoding.
                 * 4: 2 byte encoding format. Used when character codes fall into (gappy) contiguous ranges.
                 * 6: 'Trimmed table mapping', used when character codes fall into a single contiguous range. This is dense mapping.
                 * 8: 16/32 bit coverage. Uses mixed length character codes.
                 * 10: Similar to format 6, trimmed table/array for 32 bits.
                 * 12: Segmented coverage, similar to format 4 but for 32 bit/4 byte.
                 * 13: Many to one mappings. Used by Apple for the LastResort font.
                 * 14: Unicode variation sequences.
                 *
                 * Many of the formats are obsolete or not really used. Modern fonts will tend to use formats 4, 6 and 12.
                 * For PDF we will support 0, 2 and 4 since these are in the original TrueType spec.
                 */
                switch (format)
                {
                case 0:
                {
                    // Simple 1 to 1 mapping of character codes to glyph codes.
                    var item = ByteEncodingCMapTable.Load(data, subTableHeader.PlatformId, subTableHeader.EncodingId);
                    tables.Add(item);
                    break;
                }

                case 2:
                {
                    // Useful for CJK characters. Use mixed 8/16 bit encoding.
                    var item = HighByteMappingCMapTable.Load(data, numberofGlyphs, subTableHeader.PlatformId, subTableHeader.EncodingId);
                    tables.Add(item);
                    break;
                }

                case 4:
                {
                    // Microsoft's standard mapping table.
                    var item = Format4CMapTable.Load(data, subTableHeader.PlatformId, subTableHeader.EncodingId);
                    tables.Add(item);
                    break;
                }

                case 6:
                {
                    var item = TrimmedTableMappingCMapTable.Load(data, subTableHeader.PlatformId, subTableHeader.EncodingId);
                    tables.Add(item);
                    break;
                }
                }
            }

            return(new CMapTable(tableVersionNumber, header, tables));
        }
Ejemplo n.º 20
0
        private static IGlyphDescription ReadCompositeGlyph(TrueTypeDataBytes data, TemporaryCompositeLocation compositeLocation, Dictionary <int, TemporaryCompositeLocation> compositeLocations, IGlyphDescription[] glyphs,
                                                            IGlyphDescription emptyGlyph)
        {
            bool HasFlag(CompositeGlyphFlags value, CompositeGlyphFlags target)
            {
                return((value & target) == target);
            }

            data.Seek(compositeLocation.Position);

            var components = new List <CompositeComponent>();

            // First recursively find all components and ensure they are available.
            CompositeGlyphFlags flags;

            do
            {
                flags = (CompositeGlyphFlags)data.ReadUnsignedShort();
                var glyphIndex = data.ReadUnsignedShort();

                var childGlyph = glyphs[glyphIndex];

                if (childGlyph == null)
                {
                    if (!compositeLocations.TryGetValue(glyphIndex, out var missingComposite))
                    {
                        throw new InvalidOperationException($"The composite glyph required a contour at index {glyphIndex} but there was no simple or composite glyph at this location.");
                    }

                    var position = data.Position;
                    childGlyph = ReadCompositeGlyph(data, missingComposite, compositeLocations, glyphs, emptyGlyph);
                    data.Seek(position);

                    glyphs[glyphIndex] = childGlyph;
                }

                short arg1, arg2;
                if (HasFlag(flags, CompositeGlyphFlags.Args1And2AreWords))
                {
                    arg1 = data.ReadSignedShort();
                    arg2 = data.ReadSignedShort();
                }
                else
                {
                    arg1 = data.ReadByte();
                    arg2 = data.ReadByte();
                }

                decimal xscale  = 1;
                decimal scale01 = 0;
                decimal scale10 = 0;
                decimal yscale  = 1;

                if (HasFlag(flags, CompositeGlyphFlags.WeHaveAScale))
                {
                    xscale = ReadTwoFourteenFormat(data);
                    yscale = xscale;
                }
                else if (HasFlag(flags, CompositeGlyphFlags.WeHaveAnXAndYScale))
                {
                    xscale = ReadTwoFourteenFormat(data);
                    yscale = ReadTwoFourteenFormat(data);
                }
                else if (HasFlag(flags, CompositeGlyphFlags.WeHaveATwoByTwo))
                {
                    xscale  = ReadTwoFourteenFormat(data);
                    scale01 = ReadTwoFourteenFormat(data);
                    scale10 = ReadTwoFourteenFormat(data);
                    yscale  = ReadTwoFourteenFormat(data);
                }

                if (HasFlag(flags, CompositeGlyphFlags.ArgsAreXAndYValues))
                {
                    components.Add(new CompositeComponent(glyphIndex, new PdfMatrix3By2(xscale, scale01, scale10, yscale, arg1, arg2)));
                }
                else
                {
                    // TODO: Not implemented, it is unclear how to do this.
                }
            } while (HasFlag(flags, CompositeGlyphFlags.MoreComponents));

            // Now build the final glyph from the components.
            IGlyphDescription builderGlyph = null;

            foreach (var component in components)
            {
                var glyph = glyphs[component.Index];

                var transformed = glyph.Transform(component.Transformation);

                if (builderGlyph == null)
                {
                    builderGlyph = transformed;
                }
                else
                {
                    builderGlyph = builderGlyph.Merge(transformed);
                }
            }

            builderGlyph = builderGlyph ?? emptyGlyph;

            return(new Glyph(false, builderGlyph.Instructions, builderGlyph.EndPointsOfContours, builderGlyph.Points, compositeLocation.Bounds));
        }
Ejemplo n.º 21
0
        public NameTable Parse(TrueTypeHeaderTable header, TrueTypeDataBytes data, TableRegister.Builder register)
        {
            data.Seek(header.Offset);
            // ReSharper disable once UnusedVariable
            var format       = data.ReadUnsignedShort();
            var count        = data.ReadUnsignedShort();
            var stringOffset = data.ReadUnsignedShort();

            var names = new NameRecordBuilder[count];

            for (var i = 0; i < count; i++)
            {
                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 = header.Offset + stringOffset + nameRecord.Offset;

                data.Seek(position);

                var str = data.ReadString(nameRecord.Length, encoding);

                strings[i] = nameRecord.ToNameRecord(str);
            }

            return(new NameTable(header, GetName(4, strings), GetName(1, strings), GetName(2, strings), strings));
        }
Ejemplo n.º 22
0
        /// <summary>
        /// Creates a new glyph table from the input font which contains only the glyphs required by the input mapping.
        /// </summary>
        /// <param name="font">The font used to create this subset.</param>
        /// <param name="fontBytes">The raw bytes of the input font.</param>
        /// <param name="mapping">The mapping of old glyph indices to new glyph indices.</param>
        /// <returns>A new glyph table and associated information for use in creating a valid TrueType font file.</returns>
        public static TrueTypeSubsetGlyphTable SubsetGlyphTable(TrueTypeFont font, byte[] fontBytes, IndexMap[] mapping)
        {
            if (font == null)
            {
                throw new ArgumentNullException(nameof(font));
            }

            if (fontBytes == null)
            {
                throw new ArgumentNullException(nameof(fontBytes));
            }

            if (mapping == null)
            {
                throw new ArgumentNullException(nameof(mapping));
            }

            var data = new TrueTypeDataBytes(fontBytes);
            var advanceWidthTable = font.TableRegister.HorizontalMetricsTable;

            if (advanceWidthTable == null)
            {
                throw new InvalidFontFormatException($"Font: {font} did not contain a horizontal metrics table, cannot subset.");
            }

            var existingGlyphs = GetGlyphRecordsInFont(font, data);

            var glyphsToCopy = new List <GlyphRecord>(mapping.Length);
            var glyphsToCopyOriginalIndex = new List <int>(mapping.Length);

            // Extract the glyphs required for this subset from the original table.
            for (var i = 0; i < mapping.Length; i++)
            {
                var map    = mapping[i];
                var record = existingGlyphs[map.OldIndex];

                glyphsToCopy.Add(record);
                glyphsToCopyOriginalIndex.Add(map.OldIndex);
            }

            var glyphLocations = new List <uint>();
            var advanceWidths  = new List <HorizontalMetric>();

            var compositeIndicesToReplace = new List <(uint offset, ushort newIndex)>();

            using (var stream = new MemoryStream())
            {
                for (var i = 0; i < glyphsToCopy.Count; i++)
                {
                    compositeIndicesToReplace.Clear();

                    var newRecord = glyphsToCopy[i];

                    if (newRecord.Type == GlyphType.Composite)
                    {
                        // Any glyphs a composite glyph depends on must also be included.
                        for (var j = 0; j < newRecord.DependencyIndices.Count; j++)
                        {
                            // Get the indices of the dependency glyphs from the original font file.
                            var dependency = newRecord.DependencyIndices[j];

                            // If the dependency has already been included we can skip copying it again.
                            var newDependencyIndex = GetAlreadyCopiedDependencyIndex(dependency, glyphsToCopyOriginalIndex);

                            if (!newDependencyIndex.HasValue)
                            {
                                // Else we need to copy the dependency glyph from the original.
                                var actualDependencyRecord = existingGlyphs[dependency.Index];

                                // We need to add it to the set of glyphs to copy.
                                newDependencyIndex = glyphsToCopy.Count;
                                glyphsToCopy.Add(actualDependencyRecord);
                                glyphsToCopyOriginalIndex.Add((int)dependency.Index);
                            }

                            var withinGlyphDataIndexOffset = dependency.OffsetOfIndexWithinData - newRecord.Offset;

                            compositeIndicesToReplace.Add(((uint)withinGlyphDataIndexOffset, (ushort)newDependencyIndex));
                        }
                    }

                    // Record the glyph location.
                    glyphLocations.Add((uint)stream.Position);

                    var advanceWidth = advanceWidthTable.HorizontalMetrics[glyphsToCopyOriginalIndex[i]];
                    advanceWidths.Add(advanceWidth);

                    if (newRecord.Type == GlyphType.Empty)
                    {
                        // TODO: if this is the last glyph this might be a problem.
                        continue;
                    }

                    data.Seek(newRecord.Offset);

                    var glyphBytes = data.ReadByteArray(newRecord.DataLength);

                    // Update any indices referenced by composite glyphs to match the new index of the dependency.
                    foreach (var(offset, newIndex) in compositeIndicesToReplace)
                    {
                        glyphBytes[offset]     = (byte)(newIndex >> 8);
                        glyphBytes[offset + 1] = (byte)newIndex;
                    }

                    stream.Write(glyphBytes, 0, glyphBytes.Length);

                    // Each glyph description must start at a 4 byte boundary.
                    var remainder  = glyphBytes.Length % 4;
                    var bytesToPad = remainder == 0 ? 0 : 4 - remainder;
                    for (var j = 0; j < bytesToPad; j++)
                    {
                        stream.WriteByte(0);
                    }
                }

                var output = stream.ToArray();

                glyphLocations.Add((uint)output.Length);
                var offsets = glyphLocations.ToArray();

                return(new TrueTypeSubsetGlyphTable(output, offsets, advanceWidths.ToArray()));
            }
        }
Ejemplo n.º 23
0
        public static HighByteMappingCMapTable Load(TrueTypeDataBytes data, int numberOfGlyphs, int platformId, int encodingId)
        {
            // ReSharper disable UnusedVariable
            var length  = data.ReadUnsignedShort();
            var version = data.ReadUnsignedShort();
            // ReSharper restore UnusedVariable

            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(platformId, encodingId, characterCodeToGlyphId));
        }
Ejemplo n.º 24
0
        public Os2Table Parse(TrueTypeHeaderTable header, TrueTypeDataBytes data, TableRegister.Builder register)
        {
            data.Seek(header.Offset);

            var version = data.ReadUnsignedShort();

            var xAvgCharWidth       = data.ReadSignedShort();
            var weightClass         = data.ReadUnsignedShort();
            var widthClass          = data.ReadUnsignedShort();
            var typeFlags           = data.ReadUnsignedShort();
            var ySubscriptXSize     = data.ReadSignedShort();
            var ySubscriptYSize     = data.ReadSignedShort();
            var ySubscriptXOffset   = data.ReadSignedShort();
            var ySubscriptYOffset   = data.ReadSignedShort();
            var ySuperscriptXSize   = data.ReadSignedShort();
            var ySuperscriptYSize   = data.ReadSignedShort();
            var ySuperscriptXOffset = data.ReadSignedShort();
            var ySuperscriptYOffset = data.ReadSignedShort();
            var yStrikeoutSize      = data.ReadSignedShort();
            var yStrikeoutPosition  = data.ReadSignedShort();
            var familyClass         = data.ReadSignedShort();
            var panose              = data.ReadByteArray(10);
            var ulCharRange1        = data.ReadUnsignedInt();
            var ulCharRange2        = data.ReadUnsignedInt();
            var ulCharRange3        = data.ReadUnsignedInt();
            var ulCharRange4        = data.ReadUnsignedInt();
            var vendorIdBytes       = data.ReadByteArray(4);
            var selectionFlags      = data.ReadUnsignedShort();
            var firstCharacterIndex = data.ReadUnsignedShort();
            var lastCharacterIndex  = data.ReadUnsignedShort();
            var unicodeCharRange    = new[] { ulCharRange1, ulCharRange2, ulCharRange3, ulCharRange4 };

            var vendorId = Encoding.ASCII.GetString(vendorIdBytes);

            /*
             * Documentation for OS/2 version 0 in Apple’s TrueType Reference Manual stops at the usLastCharIndex field
             * and does not include the last five fields of the table as it was defined by Microsoft.
             * Some legacy TrueType fonts may have been built with a shortened version 0 OS/2 table.
             * Applications should check the table length for a version 0 OS/2 table before reading these fields.
             */
            if (version == 0 && header.Length == 68)
            {
                return(new Os2Table(header, version, xAvgCharWidth,
                                    weightClass, widthClass, typeFlags, ySubscriptXSize,
                                    ySubscriptYSize,
                                    ySubscriptXOffset,
                                    ySubscriptYOffset,
                                    ySuperscriptXSize,
                                    ySuperscriptYSize,
                                    ySuperscriptXOffset,
                                    ySuperscriptYOffset,
                                    yStrikeoutSize,
                                    yStrikeoutPosition,
                                    familyClass,
                                    panose,
                                    unicodeCharRange,
                                    vendorId,
                                    selectionFlags,
                                    firstCharacterIndex,
                                    lastCharacterIndex));
            }

            short  sTypoAscender;
            short  sTypoDescender;
            short  sTypoLineGap;
            ushort usWinAscent;
            ushort usWinDescent;

            try
            {
                sTypoAscender  = data.ReadSignedShort();
                sTypoDescender = data.ReadSignedShort();
                sTypoLineGap   = data.ReadSignedShort();
                usWinAscent    = data.ReadUnsignedShort();
                usWinDescent   = data.ReadUnsignedShort();
            }
            catch
            {
                // Font may be invalid. Try falling back to shorter version...
                return(new Os2Table(header, version, xAvgCharWidth,
                                    weightClass, widthClass, typeFlags, ySubscriptXSize,
                                    ySubscriptYSize,
                                    ySubscriptXOffset,
                                    ySubscriptYOffset,
                                    ySuperscriptXSize,
                                    ySuperscriptYSize,
                                    ySuperscriptXOffset,
                                    ySuperscriptYOffset,
                                    yStrikeoutSize,
                                    yStrikeoutPosition,
                                    familyClass,
                                    panose,
                                    unicodeCharRange,
                                    vendorId,
                                    selectionFlags,
                                    firstCharacterIndex,
                                    lastCharacterIndex));
            }

            if (version == 0)
            {
                return(new Os2RevisedVersion0Table(header, version, xAvgCharWidth,
                                                   weightClass, widthClass, typeFlags, ySubscriptXSize,
                                                   ySubscriptYSize,
                                                   ySubscriptXOffset,
                                                   ySubscriptYOffset,
                                                   ySuperscriptXSize,
                                                   ySuperscriptYSize,
                                                   ySuperscriptXOffset,
                                                   ySuperscriptYOffset,
                                                   yStrikeoutSize,
                                                   yStrikeoutPosition,
                                                   familyClass,
                                                   panose,
                                                   unicodeCharRange,
                                                   vendorId,
                                                   selectionFlags,
                                                   firstCharacterIndex,
                                                   lastCharacterIndex,
                                                   sTypoAscender,
                                                   sTypoDescender,
                                                   sTypoLineGap,
                                                   usWinAscent,
                                                   usWinDescent));
            }

            var ulCodePageRange1 = data.ReadUnsignedInt();
            var ulCodePageRange2 = data.ReadUnsignedInt();

            if (version == 1)
            {
                return(new Os2Version1Table(header, version, xAvgCharWidth,
                                            weightClass, widthClass, typeFlags, ySubscriptXSize,
                                            ySubscriptYSize,
                                            ySubscriptXOffset,
                                            ySubscriptYOffset,
                                            ySuperscriptXSize,
                                            ySuperscriptYSize,
                                            ySuperscriptXOffset,
                                            ySuperscriptYOffset,
                                            yStrikeoutSize,
                                            yStrikeoutPosition,
                                            familyClass,
                                            panose,
                                            unicodeCharRange,
                                            vendorId,
                                            selectionFlags,
                                            firstCharacterIndex,
                                            lastCharacterIndex,
                                            sTypoAscender,
                                            sTypoDescender,
                                            sTypoLineGap,
                                            usWinAscent,
                                            usWinDescent,
                                            ulCodePageRange1,
                                            ulCodePageRange2));
            }

            var sxHeight      = data.ReadSignedShort();
            var sCapHeight    = data.ReadSignedShort();
            var usDefaultChar = data.ReadUnsignedShort();
            var usBreakChar   = data.ReadUnsignedShort();
            var usMaxContext  = data.ReadUnsignedShort();

            if (version < 5)
            {
                return(new Os2Version2To4OpenTypeTable(header, version, xAvgCharWidth,
                                                       weightClass, widthClass, typeFlags, ySubscriptXSize,
                                                       ySubscriptYSize,
                                                       ySubscriptXOffset,
                                                       ySubscriptYOffset,
                                                       ySuperscriptXSize,
                                                       ySuperscriptYSize,
                                                       ySuperscriptXOffset,
                                                       ySuperscriptYOffset,
                                                       yStrikeoutSize,
                                                       yStrikeoutPosition,
                                                       familyClass,
                                                       panose,
                                                       unicodeCharRange,
                                                       vendorId,
                                                       selectionFlags,
                                                       firstCharacterIndex,
                                                       lastCharacterIndex,
                                                       sTypoAscender,
                                                       sTypoDescender,
                                                       sTypoLineGap,
                                                       usWinAscent,
                                                       usWinDescent,
                                                       ulCodePageRange1,
                                                       ulCodePageRange2,
                                                       sxHeight,
                                                       sCapHeight,
                                                       usDefaultChar,
                                                       usBreakChar,
                                                       usMaxContext));
            }

            var usLowerOpticalPointSize = data.ReadUnsignedShort();
            var usUpperOpticalPointSize = data.ReadUnsignedShort();

            return(new Os2Version5OpenTypeTable(header, version, xAvgCharWidth,
                                                weightClass, widthClass, typeFlags, ySubscriptXSize,
                                                ySubscriptYSize,
                                                ySubscriptXOffset,
                                                ySubscriptYOffset,
                                                ySuperscriptXSize,
                                                ySuperscriptYSize,
                                                ySuperscriptXOffset,
                                                ySuperscriptYOffset,
                                                yStrikeoutSize,
                                                yStrikeoutPosition,
                                                familyClass,
                                                panose,
                                                unicodeCharRange,
                                                vendorId,
                                                selectionFlags,
                                                firstCharacterIndex,
                                                lastCharacterIndex,
                                                sTypoAscender,
                                                sTypoDescender,
                                                sTypoLineGap,
                                                usWinAscent,
                                                usWinDescent,
                                                ulCodePageRange1,
                                                ulCodePageRange2,
                                                sxHeight,
                                                sCapHeight,
                                                usDefaultChar,
                                                usBreakChar,
                                                usMaxContext,
                                                usLowerOpticalPointSize,
                                                usUpperOpticalPointSize));
        }
Ejemplo n.º 25
0
        private static TrueTypeNameRecord GetTrueTypeNameRecord(NameRecordBuilder nameRecord, TrueTypeDataBytes data, uint offset)
        {
            try
            {
                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 = offset + nameRecord.Offset;

                if (position >= data.Length)
                {
                    return(null);
                }

                data.Seek(position);

                if (data.TryReadString(nameRecord.Length, encoding, out var str))
                {
                    return(nameRecord.ToNameRecord(str));
                }

                // Damaged font
                return(null);
            }
            catch
            {
                return(null);
            }
        }