/// <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); }
/// <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); }