BGI Stroked Font
A BGI Stroked Font is a vector font created by Borland to work with their 1987 graphics API, Borland Graphics Interface. Since the format stores vector fonts, it can easily be scaled in size with little loss of fidelity. However, because the format only supports lines and not curves, scaling to larger sizes will make the characters look jagged. The font files typically have a *.CHR file extension.
After 1987, Borland's C and Pascal programming languages shipped with 10 different BGI Stroked Fonts: Bold, Euro, Goth, Lcom, Litt, Sans, Scri, Simp, Trip, and Tscr. Due to the popularity of Borland's programming languages, and their graphic API, BGI Stroked Fonts were used in hundreds of DOS video games.
The designers of the file format unfortunately used a file signature starting with "PK" which would later be used by PK Zip, so BGI Stroked Font files, despite having an extension of CHR, might be misidentified as zip archives if the identifier doesn't take into account the full PK Zip signature.
The header includes the font's id, version number, and description.
|char||Signature||Must be 0x50 0x4B 0x08 0x08.|
|char[...]||Description||User text. Borland's official fonts start with "BGI Stroked Font", then the version number, and ends with a copyright. The description is terminated with: 0x1A.|
|INT16LE||Header Size||The size, in bytes, of the entire header.|
|char||Font Id||a 4-character id of the font.|
|INT16LE||Data Size||The size of the font's data block. The Header Size + Data Size should be the size of the file.|
|INT16LE||Major Version Number||The major version number of the font format.|
|INT16LE||Minor Version Number||The minor version number of the font format.|
|Nulls||Padding||The rest of the header should be padded out to nulls based on the Header Size. All official Borland fonts use a header size of 0x80.|
This section contains general information about the font including how many characters are represented, the top of the highest ascender, bottom of the lowest descender, baseline, where in the file to find each character's instructions, and the width of each character.
|char||Stroke Check||A value of 0x2B indicates this is a stoked font.|
|INT16LE||Character Count||Number of total characters stored in this font.|
|BYTE||Undefined||Borland's own documentation labels this as "undefined."|
|INT8||Starting Character||The index of the first character to have a glyph associated with it.|
|INT16LE||Strokes Offset||The file offset where stroke definitions begin.|
|BYTE||Scan Flag||Borland's documentation calls this the "scan flag," but doesn't explain what it is for.|
|INT8||Origin to Ascender||The distance from the origin to the font's highest point.|
|INT8||Origin to Baseline||The distance from the origin to the font's baseline (typically 0).|
|INT8||Origin to Descender||The distance from the origin to the font's lowest point. Note, you can derive the total character height by getting the distance from the Ascender to Descender.|
|Nulls||Padding||This section is padded with nulls until you get to the Strokes Offset. All official Borland fonts pad this section to 0x10 bytes.|
|INT16LE[Character Count]||Character Stroke Offsets||An array of the offset to the stroke definitions for each character. To get the position in the file, you must add the Header Size and the Strokes Offset to this value.|
|INT8[Character Count]||Character Widths||The unscaled width of each character.|
|Stroke Data||Character strokes||The stroke instructions for each character. See below for how to interpret them.|
This section contains the actual instructions for drawing individual characters.
|BYTE||X Instruction||Instructions for drawing the character and an X value.|
|BYTE||Y Instruction||Instructions for drawing the character and a Y value.|
|...||Several possible instruction pairs per character.|
Encoded in each two byte pair is a single stroke instruction, and an X and Y value. The most-significant bit in each byte is an opcode, the next seven bits represent a signed value. The first byte is the X value, the second byte is the Y value. See the diagram below.
7 6 543210 ^ ^ ^ | | +----------- 5-Bit number. | +------------- Sign bit for number. +--------------- Opcode.
The two opcodes determine the instruction.
|Code 1||Code 2||Instruction|
|0||0||End of character stroke definition.|
|0||1||Borland's documentation has this labeled as "do scan," implying it's related to the Scan Flag. It's only used in their BOLD font on characters with multiple objects like the colon and equals sign. It doesn't appear to affect how the character is drawn. More info is needed.|
|1||0||Move plotter to the specified X and Y coordinates.|
|1||1||Draw a line from the previous X and Y coordinates and move plotter to the new X and Y coordinates.|
The Y values for each character are stored in a unique manner. Characters are drawn relative to the baseline, but, rather than put the baseline at 0, the half above the baseline starts at 64 and decreases toward 0 as you move away from the baseline, while the half below the baseline starts at -64 and increases toward -1. See the diagram to the right.
This FreeBASIC program will draw all of the characters in the specified font, and give details about the font.
ScreenRes 1000, 800, 32 ' Open the font file. Open "SANS.CHR" For Binary As #1 ' Verify the signature. Dim As String Check Check = Space(4) Get #1, , Check If Check <> "PK" + Chr(8) + Chr(8) Then Print "Not a Borland CHR font." End End If ' Load the Description Dim As UByte Buffer Dim As String Description Do Get #1, , Buffer If Buffer <> &h1A Then Description = Description + Chr(Buffer) End If Loop Until Buffer = &h1A ' Load the header size. Dim As UShort HeaderSize Get #1, , HeaderSize ' Load the font id. Dim As String FontId FontId = Space(4) Get #1, , FontId ' Load the data file size. (HeaderSize + DataSize should match the size of the file.) Dim As UShort DataSize Get #1, , DataSize ' Load the font version number. Dim As UShort FontMajorVersion Get #1, , FontMajorVersion Dim As UShort FontMinorVersion Get #1, , FontMinorVersion ' Jump to the end of the header. The rest should be nulls anyway. Get #1, HeaderSize, Buffer ' Verify this is a BGI Stroked Font. Dim As String StrokedFontCheck StrokedFontCheck = Space(1) Get #1, , StrokedFontCheck If StrokedFontCheck <> "+" Then Print "Not a stroked font." End End If ' Load the number of characters. Dim As UShort CharacterCount Get #1, , CharacterCount ' Skip an unknown byte. Get #1, , Buffer ' Load the starting charcter. This is the first character id to have a glyph associated to it. Dim As UByte StartingChar Get #1, , StartingChar ' Load the file offset where the stroke definitions begin. Dim As UShort StrokeDefinitionOffset Get #1, , StrokeDefinitionOffset ' Skip an unknown byte. Get #1, , Buffer ' Load the distance from the origin to the top of the capital letters. Dim As Byte OriginToCapital Get #1, , OriginToCapital ' Load the distance from the origin to the baseline. Dim As Byte OriginToBaseline Get #1, , OriginToBaseline ' Load the distance from the origin to the bottom of the descenders. Dim As Byte OriginToDescender Get #1, , OriginToDescender ' Skip remaining nulls. Dim As Integer I For I = 0 To 4 Get #1, , Buffer Next I ' Load offset to characters definitions. Dim As UShort CharacterDefinitionOffset(StartingChar To (StartingChar + CharacterCount) - 1) For I = StartingChar To (StartingChar + CharacterCount) - 1 Get #1, , CharacterDefinitionOffset(I) Next I ' Load character widths. Dim As UByte CharacterWidths(StartingChar To (StartingChar + CharacterCount) - 1) For I = StartingChar To (StartingChar + CharacterCount) - 1 Get #1, , CharacterWidths(I) Next I Dim As Integer CharacterHeight = OriginToCapital + (OriginToDescender * -1) Print "Font Id: "; FontId Print "Description: "; Description Print "Total Character Height: "; CharacterHeight Print "Max Ascender: "; OriginToCapital Print "Max Descender: "; OriginToDescender Print "Total Characters: "; CharacterCount Print "Starting Character: 0x"; Hex(StartingChar) Dim As UByte XByte, YByte, Finished Dim As Integer XCurrent, YCurrent, XStart, YStart, XBuffer, YBuffer Dim As Byte XNew, YNew, XOpcode, YOpcode, XSign, YSign XStart = 10 YStart = 130 XBuffer = 5 YBuffer = 10 For I = StartingChar To (CharacterCount - StartingChar) - 1 ' Jump to the starting offset of this character. Get #1, CharacterDefinitionOffset(I) + StrokeDefinitionOffset + HeaderSize, Buffer ' Load all of the drawing instructions and draw the character. Finished = 0 Do ' Load the opcode/values. Two bytes, one for X, the other for Y. ' 7 6 543210 ' ^ ^ ^ ' | | +------ Start of 6-bit number. ' | +-------- Sign bit for number. ' +---------- Opcode bit. Get #1, , XByte ' First opcode and X value. Get #1, , YByte ' Second opcode and Y value. ' Read the opcode and sign bits. XOpcode = Bit(XByte, 7) YOpcode = Bit(YByte, 7) XSign = Bit(XByte, 6) YSign = Bit(YByte, 6) ' Clear the opcode bits and the sign bits. XByte = BitReset(XByte, 7) XByte = BitReset(XByte, 6) YByte = BitReset(YByte, 7) YByte = BitReset(YByte, 6) XNew = XByte YNew = YByte ' Set the values to negative if the signed bits are on. If XSign = True Then XNew = XNew * -1 End If If YSign = True Then YNew = (64 - YNew) ' Normalize the descender's value. YNew = YNew * -1 End If ' Normalize the Y position. YNew = (64 - YNew) If XOpcode = 0 Then If YOpcode = 0 Then ' End of character definition. Move to the next character. XStart = XStart + CharacterWidths(I) + XBuffer If XStart > 950 Then XStart = 10 YStart = YStart + CharacterHeight + YBuffer End If Finished = 1 Else ' Not sure what this is for. It's only used in the BOLD font when a character has ' multiple separate objects like with the colon and equal sign. However, not all ' characters with broken up objects in the font use it, and leaving it out doesn't ' appear to mess up the drawing at all. End If Else If YOpcode = 0 Then ' Move Pointer to X, Y. ' This happens below since both move and draw must change the position. Else ' Draw From Current Pointer to new X, Y. Line(XCurrent, YCurrent)-(XStart + XNew, YStart + YNew), RGB(255, 255, 255) End If XCurrent = XStart + XNew YCurrent = YStart + YNew End If Loop Until Finished = 1 Next I Sleep
The following tools are able to work with files in this format.
|Name||Platform||View images in this format?||Convert/export to another file/format?||Import from another file/format?||Access hidden data?||Edit metadata?||Notes|
|FE Stroke Font Editor||DOS||Yes||Yes||Yes||No||Yes||Borland's in-house font Stroked Font editor.|
This format was reverse engineered by TheAlmightyGuru. If you find this information helpful in a project you're working on, please give credit where credit is due. (A link back to this wiki would be nice too!)
- ftp.sunet.se/mirror/archive/ftp.sunet.se/pub/simtelnet/msdos/borland - Various tools for BGI fonts.