BGI Stroked Font

From ModdingWiki
Jump to navigation Jump to search
BGI Stroked Font
BGI Stroked Font.png
Format typeFont
Max glyph count32,767
Minimum glyph size (pixels)Unlimited
Maximum glyph size (pixels)Unlimited
Access modeIndexed
Metadata?ID, Description
Bitmap glyphs?No
Vector glyphs?Yes
Compressed glyphs?No
Hidden data?Yes
Games

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

Y-axis 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 PlatformView 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 DOSYesYesYesNoYes 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