I saw this format first in the source code of an earlier version of PabloDraw by Curtis Wensley aka Eto and found it very useful.
I saw it again used for a EGA/VGA DOS Font collection from 1992 by Joseph (Yossi) Gil (fntcol16.zip), which also comes with a set of tools, such as BREAKCPI.COM, which breaks the current EGA.CPI into fonts or DUMPFONT.COM, which is another tool to extract system fonts.
The fonts are naturally all bitmap fonts, mono-spaced and monochrome (black/white). The size per character is usually one of the following (Width X Height in pixels): 8×8, 8×14 or 8×16 pixels. The font files in the collection by Joseph lack the “HEADER” though, meaning the width and height information are not part of the file. He simply used the file extensions “Fxx“, where “xx” specified the height (width wasn’t needed, because all his fonts had the same width (8 pixels)). Since I added a header to all the fonts in his collection and made it part of mine, you don’t have to worry about that anymore. However, if you happen to use one of his tools to extract new fonts, you can add a header very easily by using the DOS command: “copy /b Fxx.BIN + FONTFILE.FNT NEWFONTFILE.FNT“
“Fxx.BIN” should be one of the files from this archive , depending on the font HEIGHT, e.g. F08.BIN or F16.BIN.
But enough of this and lets get to the actual stuff here.
The .FNT file format that I am introducing here allows for space efficient and relatively simple storage of monochrome (black/white) bitmap font information for the use in any kind of tool that need to emulate a specific and non-standard character set, such as the display of ANSI and ASCII text art from old MS DOS PC’s or Commodore Amiga, Atari ST/Falcon 16 bit systems or PETSCII and ATASCII art from the Commodore 64 and Atari 400/800, ZX Spectrum, Apple II 8 Bit home computers.
My font tool allows easy and quick conversion of bitmap representations of those fonts to a re-usable and generally compatible format to be used by programmers. It also has a preview feature to look at already converted .FNT files that you might get or download somewhere else.
Note: The Tool comes already with over 300 fonts, ready to use!
.FNT File Specifications
Types
- Byte – Single 8 Bits Byte/Character
- Word – 2 Bytes Unsigned Short Integer in Little Endian Byte Order
Header
Offset | Type | Length | Description | Sample Value |
---|---|---|---|---|
0 | Word | 2 | Character Width | 08 00 = Width = 8 pixels |
2 | Word | 2 | Character Height | 10 00 = Height = 16 pixels |
4 | Word | 2 | Number of Characters | 00 01 = # Chars = 255 |
Character Data
Each Point/Pixel is stored in a single bit. So for each row of a 8 pixels wide character 1 byte is used. For a 16 pixels per character wide font, 2 bytes would be used for a single row. The bit order is again little endian (right to left). Character data are stored line by line, so for an 8 pixels wide and 16 pixels high font, each character would take up 16 bytes, or 4,096 bytes for a complete set of 256 characters, making the resulting .FNT file including its 6 byte header 4,102 bytes in size.
Example for MS DOS Character 01 with character size 8 pixels wide and 16 pixels high:
Bits Alt View* 00000000 ........ 00000000 ........ 01111110 .######. 10000001 #......# 10100101 #.#..#.# 10000001 #......# 10000001 #......# 10111101 #.####.# 10011001 #..##..# 10000001 #......# 10000001 #......# 01111110 .######. 00000000 ........ 00000000 ........ 00000000 ........ 00000000 ........
*Alt View replaced 0 with . and 1 with # for better visualization
The Hex Values in the .FNT file look like this:00 00 7E 81 A5 81 81 A5 99 81 81 7E 00 00 00 00
Again as a reminder:
Because of the Little Endian bit order the Characters appear mirrowed on the Y Axis (flip the bits in a byte) in the .FNT file.
Sample Source Code (VB.NET)
Here is some sample source code for processing a .FNT font file with VB.NET
Public Module Sample Public Function ProcessFntFile(ByVal sFNTFile As System.String) _ As System.Collections.Generic.List(Of System.Drawing.Bitmap) If sFNTFile = "" Then Return Nothing Exit Function Else If Not System.IO.File.Exists(sFNTFile) Then Return Nothing Exit Function End If End If Dim lstResults As New System.Collections.Generic.List(Of System.Drawing.Bitmap) Dim lstChars As New System.Collections.Generic.List(Of System.Byte(,)) 'Build a 16 colors MS DOS PC ANSI colors palette Dim pal As System.Drawing.Imaging.ColorPalette = BuildMSDOSAnsiColorsPalette() Dim bteFC As System.Byte = 7 Dim bteBC As System.Byte = 0 Dim fntBytes() As Byte = System.IO.File.ReadAllBytes(sFNTFile) Dim lngCurrPos As System.Int64 = 0 Dim intCharWidth As System.Int32 = 0 Dim intCharHeight As System.Int32 = 0 Dim intNumChars As System.Int32 = 0 intCharWidth = GetWordLE(fntBytes, lngCurrPos) intCharHeight = GetWordLE(fntBytes, lngCurrPos) intNumChars = GetWordLE(fntBytes, lngCurrPos) 'Read all character information into a structered array 'Dimension 1 contains all lines/rows and Dimension 2 the row details (point bits) For i As System.Int32 = 0 To intNumChars - 1 Dim aChar(intCharHeight - 1, intCharWidth / 8 - 1) As System.Byte For y As System.Int32 = 0 To intCharHeight - 1 For x As System.Int32 = 0 To intCharWidth / 8 - 1 aChar(y, x) = GetByte(fntBytes, lngCurrPos) Next Next lstChars.Add(aChar) Next For i As System.Int32 = 0 To lstChars.Count - 1 Dim Img As New System.Drawing.Bitmap(intCharWidth, intCharHeight, _ System.Drawing.Imaging.PixelFormat.Format4bppIndexed) Img.Palette = pal Dim bmpSr As System.Drawing.Imaging.BitmapData = Img.LockBits( _ New System.Drawing.Rectangle(0, 0, Img.Width, Img.Height), _ System.Drawing.Imaging.ImageLockMode.ReadWrite, _ Img.PixelFormat) Dim ptrSr As System.IntPtr = bmpSr.Scan0 Dim bytesSr As System.Int32 = bmpSr.Stride * Img.Height Dim rgbvaluesSr(bytesSr) As System.Byte System.Runtime.InteropServices.Marshal.Copy(ptrSr, rgbvaluesSr, 0, bytesSr) Dim iPos As System.Int64 = 0 Dim xOff As System.Int32 = 0 Dim yOff As System.Int32 = 0 For y As System.Int32 = 0 To intCharHeight - 1 For x As System.Int32 = 0 To intCharWidth - 1 Dim arrPos As System.Int32 = Math.Floor((y * bmpSr.Stride) + (x / 2)) Dim cb As System.Int32 = Math.Floor(x / 8) Dim bitpos As System.Byte = 8 - (x - cb * 8) Dim wByte As System.Byte = rgbvaluesSr(arrPos) If x Mod 2 <> 0 Then 'odd .. right part rgbvaluesSr(arrPos) = (wByte - ((wByte << &H4) >> &H4)) + _ IIf(ExamineBit(lstChars.Item(i)(y, cb), bitpos), bteFC, bteBC) Else 'even .. left part rgbvaluesSr(arrPos) = (wByte - ((wByte >> &H4) * 16)) + _ (IIf(ExamineBit(lstChars.Item(i)(y, cb), bitpos), bteFC, bteBC) * 16) End If Next Next System.Runtime.InteropServices.Marshal.Copy(rgbvaluesSr, 0, ptrSr, bytesSr) Img.UnlockBits(bmpSr) bmpSr = Nothing rgbvaluesSr = Nothing lstResults.Add(Img.Clone) Img.Dispose() Img = Nothing Next Return lstResults End Function ' The ExamineBit function will return True or False ' depending on the value of the 1 based, nth bit (MyBit) ' of an integer (MyByte). Public Function ExamineBit(ByVal MyByte As System.Byte, ByVal MyBit As System.Byte) _ As System.Boolean Dim BitMask As System.Int16 MyByte = MyByte And &HFF BitMask = 2 ^ (MyBit - 1) Return ((MyByte And BitMask) > 0) End Function ' The SetBit Sub will set the 1 based, nth bit ' (MyBit) of an integer (MyByte). Public Sub SetBit(ByRef MyByte As System.Byte, ByVal MyBit As System.Byte) Dim BitMask As System.Int16 MyByte = MyByte And &HFF BitMask = 2 ^ (MyBit - 1) MyByte = MyByte Or BitMask End Sub Public Function GetByte(ByVal arr() As System.Byte, ByRef lngPos As System.Int64) _ As System.Byte 'Read a single byte from a byte array at specified position If lngPos < arr.Length Then lngPos += 1 Return arr(lngPos - 1) Else Return 0 End If End Function Public Function GetWordLE(ByVal arr() As System.Byte, ByRef lngPos As System.Int64) _ As System.UInt16 'read 2 bytes into an unsigned 16 bit integer little endian If lngPos + 1 < arr.Length Then Return GetByte(arr, lngPos) + (GetByte(arr, lngPos) * 256) Else Return 0 End If End Function Public Function BuildMSDOSAnsiColorsPalette() As System.Drawing.Imaging.ColorPalette Dim tmpImg As New System.Drawing.Bitmap(1, 1, _ System.Drawing.Imaging.PixelFormat.Format4bppIndexed) Dim pal As System.Drawing.Imaging.ColorPalette = tmpImg.Palette pal.Entries(0) = System.Drawing.Color.FromArgb(255, 0, 0, 0) pal.Entries(1) = System.Drawing.Color.FromArgb(255, 0, 0, 170) pal.Entries(2) = System.Drawing.Color.FromArgb(255, 0, 170, 0) pal.Entries(3) = System.Drawing.Color.FromArgb(255, 0, 170, 170) pal.Entries(4) = System.Drawing.Color.FromArgb(255, 170, 0, 0) pal.Entries(5) = System.Drawing.Color.FromArgb(255, 170, 0, 170) pal.Entries(6) = System.Drawing.Color.FromArgb(255, 170, 85, 0) pal.Entries(7) = System.Drawing.Color.FromArgb(255, 170, 170, 170) pal.Entries(8) = System.Drawing.Color.FromArgb(255, 85, 85, 85) pal.Entries(9) = System.Drawing.Color.FromArgb(255, 85, 85, 255) pal.Entries(10) = System.Drawing.Color.FromArgb(255, 85, 255, 85) pal.Entries(11) = System.Drawing.Color.FromArgb(255, 85, 255, 255) pal.Entries(12) = System.Drawing.Color.FromArgb(255, 255, 85, 85) pal.Entries(13) = System.Drawing.Color.FromArgb(255, 255, 85, 255) pal.Entries(14) = System.Drawing.Color.FromArgb(255, 255, 255, 85) pal.Entries(15) = System.Drawing.Color.FromArgb(255, 255, 255, 255) tmpImg.Dispose() tmpImg = Nothing Return pal End Function End Module
Download the sample code fntsamplevbnet.zip
Sample Source Code (C#.NET)
Here is some sample source code for processing a .FNT font file with CSharp (C#.NET)
/// <summary> /// C# generated from VB.NET Code via /// http://codeconverter.sharpdevelop.net/SnippetConverter.aspx /// (with some manual tweaks done by hand afterwards) /// This code is still untested though, but I am pretty confident /// that it will work just fine as the VB.NET version does /// Cheers! Carsten aka Roy/SAC /// </summary> public static class Sample { /// <summary> /// Reads a provided .FNT file and returns a list of images, /// one image for each character in the font set /// </summary> /// <param name="sFNTFile"></param> /// <returns></returns> public static System.Collections.Generic.List<System.Drawing.Bitmap> ProcessFntFile( System.String sFNTFile) { if (string.IsNullOrEmpty(sFNTFile)) { return null; } else { if (!System.IO.File.Exists(sFNTFile)) { return null; } } System.Collections.Generic.List<System.Drawing.Bitmap> lstResults = new System.Collections.Generic.List<System.Drawing.Bitmap>(); System.Collections.Generic.List<System.Byte[,]> lstChars = new System.Collections.Generic.List<System.Byte[,]>(); //Build a 16 colors MS DOS PC ANSI colors palette System.Drawing.Imaging.ColorPalette pal = BuildMSDOSAnsiColorsPalette(); System.Byte bteFC = 7; System.Byte bteBC = 0; byte[] fntBytes = System.IO.File.ReadAllBytes(sFNTFile); System.Int64 lngCurrPos = 0; System.Int32 intCharWidth = 0; System.Int32 intCharHeight = 0; System.Int32 intNumChars = 0; intCharWidth = GetWordLE(fntBytes, ref lngCurrPos); intCharHeight = GetWordLE(fntBytes, ref lngCurrPos); intNumChars = GetWordLE(fntBytes, ref lngCurrPos); //Read all character information into a structered array //Dimension 1 contains all lines/rows and Dimension 2 the row details (point bits) for (System.Int32 i = 0; i <= intNumChars - 1; i++) { System.Byte[,] aChar = new System.Byte[intCharHeight, intCharWidth / 8]; for (System.Int32 y = 0; y <= intCharHeight - 1; y++) { for (System.Int32 x = 0; x <= intCharWidth / 8 - 1; x++) { aChar[y, x] = GetByte(fntBytes, ref lngCurrPos); } } lstChars.Add(aChar); } for (System.Int32 i = 0; i <= lstChars.Count - 1; i++) { System.Drawing.Bitmap oImg = new System.Drawing.Bitmap(intCharWidth, intCharHeight, System.Drawing.Imaging.PixelFormat.Format4bppIndexed); oImg.Palette = pal; System.Drawing.Imaging.BitmapData bmpSr = oImg.LockBits(new System.Drawing.Rectangle(0, 0, oImg.Width, oImg.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, oImg.PixelFormat); System.IntPtr ptrSr = bmpSr.Scan0; System.Int32 bytesSr = bmpSr.Stride * oImg.Height; System.Byte[] rgbvaluesSr = new System.Byte[bytesSr + 1]; System.Runtime.InteropServices.Marshal.Copy(ptrSr, rgbvaluesSr, 0, bytesSr); for (System.Int32 y = 0; y <= intCharHeight - 1; y++) { for (System.Int32 x = 0; x <= intCharWidth - 1; x++) { System.Int32 arrPos = (int)(System.Math.Floor((y * bmpSr.Stride) + ((float)x / 2))); System.Int32 cb = (int)System.Math.Floor((float)x / 8); System.Byte bitpos = (byte)(8 - ((int)x - (int)cb * 8)); System.Byte wByte = rgbvaluesSr[arrPos]; if (x % 2 != 0) { //odd .. right part rgbvaluesSr[(int)arrPos] = (byte)((wByte - (byte)(((byte)wByte << (byte)0x4) >> (byte)0x4)) + (byte)((ExamineBit(lstChars[i][y, cb], bitpos) ? bteFC : bteBC))); } else { //even .. left part rgbvaluesSr[(int)arrPos] = (byte)((wByte - ((wByte >> 0x4) * 16)) + ((ExamineBit(lstChars[i][y, cb], bitpos) ? bteFC : bteBC) * (byte)16)); } } } System.Runtime.InteropServices.Marshal.Copy(rgbvaluesSr, 0, ptrSr, bytesSr); oImg.UnlockBits(bmpSr); bmpSr = null; rgbvaluesSr = null; lstResults.Add((System.Drawing.Bitmap) oImg.Clone()); oImg.Dispose(); oImg = null; } return lstResults; } /// <summary> /// The ExamineBit function will return True or False /// depending on the value of the 1 based, nth bit (MyBit) /// of an integer (MyByte). /// </summary> /// <param name="MyByte"></param> /// <param name="MyBit"></param> /// <returns></returns> public static System.Boolean ExamineBit(System.Byte MyByte, System.Byte MyBit) { System.Int16 BitMask = 0; MyByte = (byte)(MyByte & 0xff); BitMask = (byte)(System.Math.Pow(2, (MyBit - 1))); return ((MyByte & BitMask) > 0); } /// <summary> /// The SetBit Sub will set the 1 based, nth bit (MyBit) /// of an integer (MyByte). /// </summary> /// <param name="MyByte"></param> /// <param name="MyBit"></param> public static void SetBit(ref System.Byte MyByte, System.Byte MyBit) { System.Int16 BitMask = 0; MyByte = (byte)(MyByte & 0xff); BitMask = (byte)(System.Math.Pow(2, (MyBit - 1))); MyByte = (byte)(MyByte | (byte)BitMask); } /// <summary> /// Read a single byte from a byte array at specified position /// </summary> /// <param name="arr"></param> /// <param name="lngPos"></param> /// <returns></returns> public static System.Byte GetByte(System.Byte[] arr, ref System.Int64 lngPos) { if (lngPos < arr.Length) { lngPos += 1; return arr[lngPos - 1]; } else { return 0; } } /// <summary> /// read 2 bytes into an unsigned 16 bit integer little endian /// </summary> /// <param name="arr"></param> /// <param name="lngPos"></param> /// <returns></returns> public static System.UInt16 GetWordLE(System.Byte[] arr, ref System.Int64 lngPos) { if (lngPos + 1 < arr.Length) { return (ushort)(GetByte(arr, ref lngPos) + (GetByte(arr, ref lngPos) * 256)); } else { return 0; } } /// <summary> /// Build Ansi Colors Image Palette (16 Colors) /// </summary> /// <returns></returns> public static System.Drawing.Imaging.ColorPalette BuildMSDOSAnsiColorsPalette() { System.Drawing.Bitmap tmpImg = new System.Drawing.Bitmap(1, 1, System.Drawing.Imaging.PixelFormat.Format4bppIndexed); System.Drawing.Imaging.ColorPalette pal = tmpImg.Palette; pal.Entries[0] = System.Drawing.Color.FromArgb(255, 0, 0, 0); pal.Entries[1] = System.Drawing.Color.FromArgb(255, 0, 0, 170); pal.Entries[2] = System.Drawing.Color.FromArgb(255, 0, 170, 0); pal.Entries[3] = System.Drawing.Color.FromArgb(255, 0, 170, 170); pal.Entries[4] = System.Drawing.Color.FromArgb(255, 170, 0, 0); pal.Entries[5] = System.Drawing.Color.FromArgb(255, 170, 0, 170); pal.Entries[6] = System.Drawing.Color.FromArgb(255, 170, 85, 0); pal.Entries[7] = System.Drawing.Color.FromArgb(255, 170, 170, 170); pal.Entries[8] = System.Drawing.Color.FromArgb(255, 85, 85, 85); pal.Entries[9] = System.Drawing.Color.FromArgb(255, 85, 85, 255); pal.Entries[10] = System.Drawing.Color.FromArgb(255, 85, 255, 85); pal.Entries[11] = System.Drawing.Color.FromArgb(255, 85, 255, 255); pal.Entries[12] = System.Drawing.Color.FromArgb(255, 255, 85, 85); pal.Entries[13] = System.Drawing.Color.FromArgb(255, 255, 85, 255); pal.Entries[14] = System.Drawing.Color.FromArgb(255, 255, 255, 85); pal.Entries[15] = System.Drawing.Color.FromArgb(255, 255, 255, 255); tmpImg.Dispose(); tmpImg = null; return pal; } }
Download the sample code fntsamplec#net.zip
Enjoy!
Carsten aka Roy/SAC
[…] « A Little .FNT Tool for Programmers […]