Example #1
0
        static Metadata LoadMetadata(Stream stream)
        {
            using (var reader = new DataReader(stream)) {
                var tables  = SfntTables.ReadFaceHeader(reader);
                var names   = SfntTables.ReadNames(reader, tables);
                var os2Data = SfntTables.ReadOS2(reader, tables);

                return(new Metadata {
                    Family = names.TypographicFamilyName ?? names.FamilyName,
                    Weight = os2Data.Weight,
                    Stretch = os2Data.Stretch,
                    Style = os2Data.Style
                });
            }
        }
Example #2
0
        public static CharacterMap ReadCmap(DataReader reader, TableRecord[] tables)
        {
            SfntTables.SeekToTable(reader, tables, FourCC.Cmap, required: true);

            // skip version
            var cmapOffset = reader.Position;

            reader.Skip(sizeof(short));

            // read all of the subtable headers
            var subtableCount   = reader.ReadUInt16BE();
            var subtableHeaders = new CmapSubtableHeader[subtableCount];

            for (int i = 0; i < subtableHeaders.Length; i++)
            {
                subtableHeaders[i] = new CmapSubtableHeader
                {
                    PlatformID = reader.ReadUInt16BE(),
                    EncodingID = reader.ReadUInt16BE(),
                    Offset     = reader.ReadUInt32BE()
                };
            }

            // search for a "full" Unicode table first
            var chosenSubtableOffset = 0u;

            for (int i = 0; i < subtableHeaders.Length; i++)
            {
                var platform = subtableHeaders[i].PlatformID;
                var encoding = subtableHeaders[i].EncodingID;
                if ((platform == PlatformID.Microsoft && encoding == WindowsEncoding.UnicodeFull) ||
                    (platform == PlatformID.Unicode && encoding == UnicodeEncoding.Unicode32))
                {
                    chosenSubtableOffset = subtableHeaders[i].Offset;
                    break;
                }
            }

            // if no full unicode table, just grab the first
            // one that supports any flavor of Unicode
            if (chosenSubtableOffset == 0)
            {
                for (int i = 0; i < subtableHeaders.Length; i++)
                {
                    var platform = subtableHeaders[i].PlatformID;
                    var encoding = subtableHeaders[i].EncodingID;
                    if ((platform == PlatformID.Microsoft && encoding == WindowsEncoding.UnicodeBmp) ||
                        platform == PlatformID.Unicode)
                    {
                        chosenSubtableOffset = subtableHeaders[i].Offset;
                        break;
                    }
                }
            }

            // no unicode support at all is an error
            if (chosenSubtableOffset == 0)
            {
                throw new Exception("Font does not support Unicode.");
            }

            // jump to our chosen table and find out what format it's in
            reader.Seek(cmapOffset + chosenSubtableOffset);
            var format = reader.ReadUInt16BE();

            switch (format)
            {
            case 4: return(ReadCmapFormat4(reader));

            default: throw new Exception("Unsupported cmap format.");
            }
        }
Example #3
0
        /// <summary>
        /// Initializes a new instance of the <see cref="FontFace"/> class.
        /// </summary>
        /// <param name="stream">A stream pointing to the font file.</param>
        /// <remarks>
        /// All relevant font data is loaded into memory and retained by the FontFace object.
        /// Once the constructor finishes you are free to close the stream.
        /// </remarks>
        public FontFace(Stream stream)
        {
            // read the face header and table records
            using (var reader = new DataReader(stream))
            {
                var tables = SfntTables.ReadFaceHeader(reader);

                // read head and maxp tables for font metadata and limits
                FaceHeader head;
                SfntTables.ReadHead(reader, tables, out head);
                SfntTables.ReadMaxp(reader, tables, ref head);
                unitsPerEm   = head.UnitsPerEm;
                integerPpems = (head.Flags & HeadFlags.IntegerPpem) != 0;

                // horizontal metrics header and data
                SfntTables.SeekToTable(reader, tables, FourCC.Hhea, required: true);
                var hMetricsHeader = SfntTables.ReadMetricsHeader(reader);
                SfntTables.SeekToTable(reader, tables, FourCC.Hmtx, required: true);
                hmetrics = SfntTables.ReadMetricsTable(reader, head.GlyphCount, hMetricsHeader.MetricCount);

                // font might optionally have vertical metrics
                if (SfntTables.SeekToTable(reader, tables, FourCC.Vhea))
                {
                    var vMetricsHeader = SfntTables.ReadMetricsHeader(reader);

                    SfntTables.SeekToTable(reader, tables, FourCC.Vmtx, required: true);
                    vmetrics = SfntTables.ReadMetricsTable(reader, head.GlyphCount, vMetricsHeader.MetricCount);
                }

                // OS/2 table has even more metrics
                var os2Data = SfntTables.ReadOS2(reader, tables);
                xHeight   = os2Data.XHeight;
                capHeight = os2Data.CapHeight;
                Weight    = os2Data.Weight;
                Stretch   = os2Data.Stretch;
                Style     = os2Data.Style;

                // optional PostScript table has random junk in it
                SfntTables.ReadPost(reader, tables, ref head);
                IsFixedWidth = head.IsFixedPitch;

                // read character-to-glyph mapping tables and kerning table
                charMap   = CharacterMap.ReadCmap(reader, tables);
                kernTable = KerningTable.ReadKern(reader, tables);

                // name data
                var names = SfntTables.ReadNames(reader, tables);
                Family      = names.TypographicFamilyName ?? names.FamilyName;
                Subfamily   = names.TypographicSubfamilyName ?? names.SubfamilyName;
                FullName    = names.FullName;
                UniqueID    = names.UniqueID;
                Version     = names.Version;
                Description = names.Description;

                // load glyphs if we have them
                if (SfntTables.SeekToTable(reader, tables, FourCC.Glyf))
                {
                    unsafe
                    {
                        // read in the loca table, which tells us the byte offset of each glyph
                        var loca = stackalloc uint[head.GlyphCount];
                        SfntTables.ReadLoca(reader, tables, head.IndexFormat, loca, head.GlyphCount);

                        // we need to know the length of the glyf table because of some weirdness in the loca table:
                        // if a glyph is "missing" (like a space character), then its loca[n] entry is equal to loca[n+1]
                        // if the last glyph in the set is missing, then loca[n] == glyf table length
                        SfntTables.SeekToTable(reader, tables, FourCC.Glyf);
                        var glyfOffset = reader.Position;
                        var glyfLength = tables[SfntTables.FindTable(tables, FourCC.Glyf)].Length;

                        // read in all glyphs
                        glyphs = new BaseGlyph[head.GlyphCount];
                        for (int i = 0; i < glyphs.Length; i++)
                        {
                            SfntTables.ReadGlyph(reader, i, 0, glyphs, glyfOffset, glyfLength, loca);
                        }
                    }
                }

                // embedded bitmaps
                SbitTable.Read(reader, tables);

                // metrics calculations: if the UseTypographicMetrics flag is set, then
                // we should use the sTypo*** data for line height calculation
                if (os2Data.UseTypographicMetrics)
                {
                    // include the line gap in the ascent so that
                    // white space is distributed above the line
                    cellAscent  = os2Data.TypographicAscender + os2Data.TypographicLineGap;
                    cellDescent = -os2Data.TypographicDescender;
                    lineHeight  = os2Data.TypographicAscender + os2Data.TypographicLineGap - os2Data.TypographicDescender;
                }
                else
                {
                    // otherwise, we need to guess at whether hhea data or os/2 data has better line spacing
                    // this is the recommended procedure based on the OS/2 spec extra notes
                    cellAscent  = os2Data.WinAscent;
                    cellDescent = Math.Abs(os2Data.WinDescent);
                    lineHeight  = Math.Max(
                        Math.Max(0, hMetricsHeader.LineGap) + hMetricsHeader.Ascender + Math.Abs(hMetricsHeader.Descender),
                        cellAscent + cellDescent
                        );
                }

                // give sane defaults for underline and strikeout data if missing
                underlineSize = head.UnderlineThickness != 0 ?
                                head.UnderlineThickness : (head.UnitsPerEm + 7) / 14;
                underlinePosition = head.UnderlinePosition != 0 ?
                                    head.UnderlinePosition : -((head.UnitsPerEm + 5) / 10);
                strikeoutSize = os2Data.StrikeoutSize != 0 ?
                                os2Data.StrikeoutSize : underlineSize;
                strikeoutPosition = os2Data.StrikeoutPosition != 0 ?
                                    os2Data.StrikeoutPosition : head.UnitsPerEm / 3;

                // create some vertical metrics in case we haven't loaded any
                verticalSynthesized = new MetricsEntry
                {
                    FrontSideBearing = os2Data.TypographicAscender,
                    Advance          = os2Data.TypographicAscender - os2Data.TypographicDescender
                };

                // read in global font program data
                controlValueTable = SfntTables.ReadCvt(reader, tables);
                prepProgram       = SfntTables.ReadProgram(reader, tables, FourCC.Prep);
                interpreter       = new Interpreter(
                    head.MaxStackSize,
                    head.MaxStorageLocations,
                    head.MaxFunctionDefs,
                    head.MaxInstructionDefs,
                    head.MaxTwilightPoints
                    );

                // the fpgm table optionally contains a program to run at initialization time
                var fpgm = SfntTables.ReadProgram(reader, tables, FourCC.Fpgm);
                if (fpgm != null)
                {
                    interpreter.InitializeFunctionDefs(fpgm);
                }
            }

            Id = Interlocked.Increment(ref currentId);
        }
Example #4
0
        public unsafe static SbitTable Read(DataReader reader, TableRecord[] tables)
        {
            if (!SfntTables.SeekToTable(reader, tables, FourCC.Eblc))
            {
                return(null);
            }

            // skip version
            var baseOffset = reader.Position;

            reader.Skip(sizeof(int));

            // load each strike table
            var count = reader.ReadInt32BE();

            if (count > MaxBitmapStrikes)
            {
                throw new Exception("Too many bitmap strikes in font.");
            }

            var sizeTableHeaders = stackalloc BitmapSizeTable[count];

            for (int i = 0; i < count; i++)
            {
                sizeTableHeaders[i].SubTableOffset = reader.ReadUInt32BE();
                sizeTableHeaders[i].SubTableSize   = reader.ReadUInt32BE();
                sizeTableHeaders[i].SubTableCount  = reader.ReadUInt32BE();

                // skip colorRef, metrics entries, start and end glyph indices
                reader.Skip(sizeof(uint) + sizeof(ushort) * 2 + 12 * 2);

                sizeTableHeaders[i].PpemX    = reader.ReadByte();
                sizeTableHeaders[i].PpemY    = reader.ReadByte();
                sizeTableHeaders[i].BitDepth = reader.ReadByte();
                sizeTableHeaders[i].Flags    = (BitmapSizeFlags)reader.ReadByte();
            }

            // read index subtables
            var indexSubTables = stackalloc IndexSubTable[count];

            for (int i = 0; i < count; i++)
            {
                reader.Seek(baseOffset + sizeTableHeaders[i].SubTableOffset);
                indexSubTables[i] = new IndexSubTable
                {
                    FirstGlyph = reader.ReadUInt16BE(),
                    LastGlyph  = reader.ReadUInt16BE(),
                    Offset     = reader.ReadUInt32BE()
                };
            }

            // read the actual data for each strike table
            for (int i = 0; i < count; i++)
            {
                // read the subtable header
                reader.Seek(baseOffset + sizeTableHeaders[i].SubTableOffset + indexSubTables[i].Offset);
                var indexFormat     = reader.ReadUInt16BE();
                var imageFormat     = reader.ReadUInt16BE();
                var imageDataOffset = reader.ReadUInt32BE();
            }

            return(null);
        }
Example #5
0
        public static KerningTable ReadKern(DataReader reader, TableRecord[] tables)
        {
            // kern table is optional
            if (!SfntTables.SeekToTable(reader, tables, FourCC.Kern))
            {
                return(null);
            }

            // skip version
            reader.Skip(sizeof(short));

            // read each subtable and accumulate kerning values
            var tableData     = new Dictionary <uint, int>();
            var subtableCount = reader.ReadUInt16BE();

            for (int i = 0; i < subtableCount; i++)
            {
                // skip version
                var currentOffset = reader.Position;
                reader.Skip(sizeof(short));

                var length   = reader.ReadUInt16BE();
                var coverage = reader.ReadUInt16BE();

                // we (and Windows) only support Format 0 tables
                // only care about tables with horizontal kerning data
                var kc = (KernCoverage)coverage;
                if ((coverage & FormatMask) == 0 && (kc & KernCoverage.Horizontal) != 0 && (kc & KernCoverage.CrossStream) == 0)
                {
                    // read the number of entries; skip over the rest of the header
                    var entryCount = reader.ReadUInt16BE();
                    reader.Skip(sizeof(short) * 3);

                    var isMin      = (kc & KernCoverage.Minimum) != 0;
                    var isOverride = (kc & KernCoverage.Override) != 0;

                    // read in each entry and accumulate its kerning data
                    for (int j = 0; j < entryCount; j++)
                    {
                        var left  = reader.ReadUInt16BE();
                        var right = reader.ReadUInt16BE();
                        var value = reader.ReadInt16BE();

                        // look up the current value, if we have one; if not, start at zero
                        int current = 0;
                        var key     = ((uint)left << 16) | right;
                        tableData.TryGetValue(key, out current);

                        if (isMin)
                        {
                            if (current < value)
                            {
                                tableData[key] = value;
                            }
                        }
                        else if (isOverride)
                        {
                            tableData[key] = value;
                        }
                        else
                        {
                            tableData[key] = current + value;
                        }
                    }
                }

                // jump to the next subtable
                reader.Seek(currentOffset + length);
            }

            return(new KerningTable(tableData));
        }