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.
Format
Header
The header includes the font's id, version number, and description.
Data Type | Name | Description |
---|---|---|
char[4] | 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[4] | 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. |
Stroke Header
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.
Data Type | Name | Description |
---|---|---|
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. |
Stroke Data
This section contains the actual instructions for drawing individual characters.
Data Type | Name | Description |
---|---|---|
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. |
Character Layout
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.
Source Code
Shape Viewer
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
Tools
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. |
Credits
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!)
Links
- ftp.sunet.se/mirror/archive/ftp.sunet.se/pub/simtelnet/msdos/borland - Various tools for BGI fonts.