// Reference: http://support.microsoft.com/kb/65123 using System; using System.IO; using System.Collections.Generic; namespace Orvid.Graphics.FontSupport.fnt { internal class FntLoader { #region Fields /// /// 2 bytes specifying the version (0200H or 0300H) of /// the file. /// public ushort Version; /// /// 4 bytes specifying the total size of the file in /// bytes. /// public uint FileSize; /// /// 60 bytes specifying copyright information. /// public string Copyright; /// /// 2 bytes specifying the type of font file. /// /// The low-order byte is exclusively for GDI use. If the /// low-order bit of the WORD is zero, it is a bitmap /// (raster) font file. If the low-order bit is 1, it is a /// vector font file. The second bit is reserved and must /// be zero. If no bits follow in the file and the bits are /// located in memory at a fixed address specified in /// dfBitsOffset, the third bit is set to 1; otherwise, the /// bit is set to 0 (zero). The high-order bit of the low /// byte is set if the font was realized by a device. The /// remaining bits in the low byte are reserved and set to /// zero. /// /// The high byte is reserved for device use and will /// always be set to zero for GDI-realized standard fonts /// Physical fonts with the high-order bit of the low byte /// set may use this byte to describe themselves. GDI will /// never inspect the high byte. /// public ushort Type; /// /// 2 bytes specifying the nominal point size at which /// this character set looks best. /// public ushort PointSize; /// /// 2 bytes specifying the nominal vertical resolution /// (dots-per-inch) at which this character set was /// digitized. /// public ushort VertDpi; /// /// 2 bytes specifying the nominal horizontal resolution /// (dots-per-inch) at which this character set was /// digitized. /// public ushort HorizDpi; /// /// 2 bytes specifying the distance from the top of a /// character definition cell to the baseline of the /// typographical font. It is useful for aligning the /// baselines of fonts of different heights. /// public ushort Ascent; /// /// Specifies the amount of leading inside the bounds set /// by dfPixHeight. Accent marks may occur in this area. /// This may be zero at the designer's option. /// public ushort InternalLeading; /// /// Specifies the amount of extra leading that the designer /// requests the application add between rows. Since this /// area is outside of the font proper, it contains no /// marks and will not be altered by text output calls in /// either the OPAQUE or TRANSPARENT mode. This may be zero /// at the designer's option. /// public ushort ExternalLeading; public bool IsItalic; public bool IsUnderline; public bool IsStrikeOut; /// /// 2 bytes specifying the weight of the characters in the /// character definition data, on a scale of 1 to 1000. A /// dfWeight of 400 specifies a regular weight. /// public ushort Weight; /// /// 1 byte specifying the character set defined by this /// font. /// public byte CharSet; /// /// 2 bytes specifing the width. /// public ushort PixWidth; /// /// 2 bytes specifying the height of the character bitmap /// (raster fonts), or the height of the grid on which a /// vector font was digitized. /// public ushort PixHeight; /// /// Specifies the pitch and font family. The low bit is set /// if the font is variable pitch. The high four bits give /// the family name of the font. Font families describe in /// a general way the look of a font. They are intended for /// specifying fonts when the exact face name desired is /// not available. The families are as follows: /// /// Family Description /// ------ ----------- /// FF_DONTCARE (0<<4) Don't care or don't know. /// FF_ROMAN (1<<4) Proportionally spaced fonts with serifs. /// FF_SWISS (2<<4) Proportionally spaced fonts without serifs. /// FF_MODERN (3<<4) Fixed-pitch fonts. /// FF_SCRIPT (4<<4) /// FF_DECORATIVE (5<<4) /// public byte PitchAndFamily; /// /// 2 bytes specifying the width of characters in the font. /// For fixed-pitch fonts, this is the same as dfPixWidth. /// For variable-pitch fonts, this is the width of the /// character "X." /// public ushort AvgWidth; /// /// 2 bytes specifying the maximum pixel width of any /// character in the font. For fixed-pitch fonts, this is /// simply dfPixWidth. /// public ushort MaxWidth; /// /// 1 byte specifying the first character code defined by /// this font. Character definitions are stored only for /// the characters actually present in a font. Therefore, /// use this field when calculating indexes into either /// dfBits or dfCharOffset. /// public byte FirstChar; /// /// 1 byte specifying the last character code defined by /// this font. Note that all the characters with codes /// between dfFirstChar and dfLastChar must be present in /// the font character definitions. /// public byte LastChar; /// /// 1 byte specifying the character to substitute /// whenever a string contains a character out of the /// range. The character is given relative to dfFirstChar /// so that dfDefaultChar is the actual value of the /// character, less dfFirstChar. The dfDefaultChar should /// indicate a special character that is not a space. /// public byte DefaultChar; /// /// 1 byte specifying the character that will define word /// breaks. This character defines word breaks for word /// wrapping and word spacing justification. The character /// is given relative to dfFirstChar so that dfBreakChar is /// the actual value of the character, less that of /// dfFirstChar. The dfBreakChar is normally (32 - /// dfFirstChar), which is an ASCII space. /// public byte BreakChar; /// /// 2 bytes specifying the number of bytes in each row of /// the bitmap. This is always even, so that the rows start /// on WORD boundaries. For vector fonts, this field has no /// meaning. /// public ushort WidthBytes; /// /// 4 bytes specifying the offset in the file to the /// null-terminated string giving the device name. /// For a generic font, this value is zero. /// public uint Device; /// /// 4 bytes specifying the offset in the file to the /// null-terminated string that names the face. /// public uint Face; /// /// 4 bytes specifying the absolute machine address of /// the bitmap. This is set by GDI at load time. The /// dfBitsPointer is guaranteed to be even. /// public uint BitsPointer; /// /// 4 bytes specifying the offset in the file to the /// beginning of the bitmap information. If the 04H bit in /// the dfType is set, then dfBitsOffset is an absolute /// address of the bitmap (probably in ROM). /// /// For raster fonts, dfBitsOffset points to a sequence of /// bytes that make up the bitmap of the font, whose height /// is the height of the font, and whose width is the sum /// of the widths of the characters in the font rounded up /// to the next WORD boundary. /// /// For vector fonts, it points to a string of bytes or /// words (depending on the size of the grid on which the /// font was digitized) that specify the strokes for each /// character of the font. The dfBitsOffset field must be /// even. /// public uint BitsOffset; /// /// 4 bytes specifying the bits flags, which are additional /// flags that define the format of the Glyph bitmap, as /// follows: /// /// DFF_FIXED equ 0001h ; font is fixed pitch /// DFF_PROPORTIONAL equ 0002h ; font is proportional pitch /// DFF_ABCFIXED equ 0004h ; font is an ABC fixed font /// DFF_ABCPROPORTIONAL equ 0008h ; font is an ABC pro-portional font /// DFF_1COLOR equ 0010h ; font is one color /// DFF_16COLOR equ 0020h ; font is 16 color /// DFF_256COLOR equ 0040h ; font is 256 color /// DFF_RGBCOLOR equ 0080h ; font is RGB color /// public uint Flags; /// /// 2 bytes specifying the global A space, if any. The /// dfAspace is the distance from the current position to /// the left edge of the bitmap. /// public ushort ASpace; /// /// 2 bytes specifying the global B space, if any. The /// dfBspace is the width of the character. /// public ushort BSpace; /// /// 2 bytes specifying the global C space, if any. The /// dfCspace is the distance from the right edge of the /// bitmap to the new current position. The increment of a /// character is the sum of the three spaces. These apply /// to all glyphs and is the case for DFF_ABCFIXED. /// public ushort CSpace; /// /// 4 bytes specifying the offset to the color table for /// color fonts, if any. The format of the bits is similar /// to a DIB, but without the header. That is, the /// characters are not split up into disjoint bytes. /// Instead, they are left intact. If no color table is /// needed, this entry is NULL. /// public uint ColorPointer; public FntGlyph[] Glyphs = new FntGlyph[255]; public string DeviceName; public string FaceName; public FntGlyphType GlyphType; public FntGlyph NotDefChar; private const byte FixedFlagMask = 1; private const byte ProportionalFlagMask = 2; private const byte ABCFixedFlagMask = 4; private const byte ABCProportionalFlagMask = 8; private const byte Color1FlagMask = 16; private const byte Color16FlagMask = 32; private const byte Color256FlagMask = 64; private const byte ColorRGBFlagMask = 128; #endregion public FntLoader() { } public string GetFamily() { if (PitchAndFamily == 0) { return "None"; } else if (PitchAndFamily == (1<<4)) { return "Roman"; } else if (PitchAndFamily == (2<<4)) { return "Swiss"; } else if (PitchAndFamily == (3<<4)) { return "Modern"; } else if (PitchAndFamily == (4<<4)) { return "Script"; } else if (PitchAndFamily == (5<<4)) { return "Decorative"; } else { return "Unknown"; } } public Rectangle GetBounds() { Rectangle r = new Rectangle(); r.SetBounds(this.CSpace, this.Ascent, this.MaxWidth, this.PixHeight); return r; } public int GetDepth() { switch (GlyphType) { case FntGlyphType.Version2: case FntGlyphType.Color1: return 1; default: return 900; } } public FontStyle GetStyle() { FontStyle f = FontStyle.Normal; if (IsItalic) f |= FontStyle.Italic; if (IsStrikeOut) f |= FontStyle.Strikeout; if (IsUnderline) f |= FontStyle.Underline; if (Weight >= 800) f |= FontStyle.Bold; return f; } public FntGlyph GetGlyph(char c) { if (((int)c) > 255) return NotDefChar; FntGlyph g = Glyphs[((int)c) - 1]; if (g == null) return NotDefChar; return g; } public int GetSize() { return PointSize; } public void Load(Stream s) { BinaryReader br = new BinaryReader(s); byte[] buf; Version = br.ReadUInt16(); if (Version != 0x0300 && Version != 0x0200) { throw new Exception("Unknown format version!"); } FileSize = br.ReadUInt32(); if (FileSize != s.Length) { throw new Exception("FileSize incorrect! Possible corrupt file?"); } buf = br.ReadBytes(60); Copyright = System.Text.ASCIIEncoding.ASCII.GetString(buf).Replace("\0", ""); buf = null; Type = br.ReadUInt16(); if ((Type & 1) == 1) { throw new Exception("Can't handle Vector FNT files."); } PointSize = br.ReadUInt16(); VertDpi = br.ReadUInt16(); HorizDpi = br.ReadUInt16(); Ascent = br.ReadUInt16(); InternalLeading = br.ReadUInt16(); ExternalLeading = br.ReadUInt16(); if (br.ReadByte() != 0) { IsItalic = true; } else { IsItalic = false; } if (br.ReadByte() != 0) { IsUnderline = true; } else { IsUnderline = false; } if (br.ReadByte() != 0) { IsStrikeOut = true; } else { IsStrikeOut = false; } Weight = br.ReadUInt16(); CharSet = br.ReadByte(); PixWidth = br.ReadUInt16(); PixHeight = br.ReadUInt16(); PitchAndFamily = br.ReadByte(); AvgWidth = br.ReadUInt16(); MaxWidth = br.ReadUInt16(); FirstChar = br.ReadByte(); LastChar = br.ReadByte(); DefaultChar = br.ReadByte(); BreakChar = br.ReadByte(); WidthBytes = br.ReadUInt16(); long position; string deviceName = ""; char curChar = ' '; #region Read Device Name Device = br.ReadUInt32(); position = br.BaseStream.Position; br.BaseStream.Position = Device; br.BaseStream.Flush(); while (curChar != '\u0000') { deviceName += curChar; curChar = (char)br.ReadByte(); } br.BaseStream.Position = position; br.BaseStream.Flush(); DeviceName = deviceName.Substring(1); #endregion #region Read Face Name Face = br.ReadUInt32(); deviceName = ""; // re-use the variable. curChar = ' '; position = br.BaseStream.Position; br.BaseStream.Position = Face; br.BaseStream.Flush(); // loop while it's not the null terminator. while (curChar != '\u0000') { deviceName += curChar; curChar = (char)br.ReadByte(); } br.BaseStream.Position = position; br.BaseStream.Flush(); // account for the extra char we added. FaceName = deviceName.Substring(1); #endregion BitsPointer = br.ReadUInt32(); BitsOffset = br.ReadUInt32(); br.ReadByte(); // Not used. if (Version == 0x0300) { Flags = br.ReadUInt32(); ASpace = br.ReadUInt16(); BSpace = br.ReadUInt16(); CSpace = br.ReadUInt16(); ColorPointer = br.ReadUInt32(); br.ReadBytes(16); // Not used. } // Next is the character table. List chars = new List(); FntGlyph g; uint nChars = (uint)((LastChar - FirstChar) + 2); position = 0; long loc; int bytesToRead; if (Version == 0x0200) { this.GlyphType = FntGlyphType.Version2; for (uint c = 0; c < nChars; c++) { g = new FntGlyph(); g.Width = br.ReadUInt16(); g.Type = FntGlyphType.Version2; g.Height = PixHeight; g.Font = this; #region Read Data loc = br.ReadUInt16(); position = br.BaseStream.Position; br.BaseStream.Position = loc; br.BaseStream.Flush(); bytesToRead = (int)(((g.Width + 7) >> 3) * (PixHeight)); g.RawData = br.ReadBytes(bytesToRead); br.BaseStream.Position = position; br.BaseStream.Flush(); #endregion chars.Add(g); } } for (int i = 0; i < chars.Count; i++) { chars[i].Initialize(); } Array.Copy(chars.ToArray(), 0, Glyphs, FirstChar - 1, chars.Count - 1); //Glyphs = chars; NotDefChar = chars[chars.Count - 1]; //throw new Exception(); //else // version == 0x0300 //{ // if ((Flags & FixedFlagMask) == FixedFlagMask || (Flags & ProportionalFlagMask) == ProportionalFlagMask) // { // FntGlyphType t; // if ((Flags & FixedFlagMask) == FixedFlagMask) // { // t = FntGlyphType.Fixed; // } // else // { // t = FntGlyphType.Proportional; // } // for (uint c = 0; c < nChars; c++) // { // g = new FntGlyph(); // g.Type = t; // } // } // else if ((Flags & ABCFixedFlagMask) == ABCFixedFlagMask || (Flags & ABCProportionalFlagMask) == ABCProportionalFlagMask) // { // for (uint c = 0; c < nChars; c++) // { // } // } // else // A color flag. // { // for (uint c = 0; c < nChars; c++) // { // } // } //} } } }