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?Name, Description
Bitmap glyphs?No
Vector glyphs?Yes
Compressed glyphs?No
Hidden data?Yes
Games

A BGI Stroked Font is a vector font format 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 and software.

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 name, version number, and description.

Data Type Name Description
char[2] Signature Must be 0x50 0x4B ("PK"). Probably initials of Borland founder Philippe Kahn.
char[...] Description User text. Borland's official fonts start with "BGI Stroked Font" with two 0x08 characters before to hide font file signature in case of use DOS "type" command on font file, then the version number, and ends with a copyright. The description is terminated with byte 0x1A which Borland font loader code search in first 256 bytes of the file (including two bytes of Signature above). In other words this field size can be from 0 (zero - no copyright text at all) to 253 bytes long (256 - 3).
char Header Start Must be 0x1A.
UINT16LE Header Size The size, in bytes, of the entire header.
char[4] Font Name A 4-character font internal name.
UINT16LE Font Size The size of the font's data block. The Header Size + Font Size should be the size of the file. See remarks according Font Size below.
UINT8 Font Major Driver Version Information major number. Must be 1 (Borland font loader also accepts anything bigger but not 0, i.e. valid if "value >= 1").
UINT8 Font Minor Driver Version Information minor number. Ignored, should be 0.
UINT8 Revision Major BGI Revision Information major number. Must be 1 (Borland font loader also accepts 0 but halts on anything bigger than 1, i.e. valid if "value <= 1").
UINT8 Revision Minor BGI Revision Information minor number. Ignored, should be 0.
char[...] 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 (note that font editors may not work with fonts with different header size). This field must be at least 15 bytes long since Borland C code aligns Stroke Header to 16 byte bounday by moving Font Size bytes back inplace. See remarks according Font Size below.

Correct Font Size field may not be important for Borland Pascal but is crucial for Borlad C since their library function register(far)bgifont moving font data inplace to align on 16 byte boundary based on this field value. User-made fonts with incorrect Font Size (less than it should be) and/or with Padding less than 15 bytes may be corrupted in memory and will not work properly under Borland C! It doesn't matter if Stroke Header itself aligned in file to 16 byte boundary because Borland C code checks only actual memory address (it may be anything).

To calculate proper Font Size do as follows:

  1. Find max offset in Character Stroke Offsets table (see Stroke Header definition below).
  2. Add Strokes Offset to get the Stroke Data offset for that character.
  3. Parse Stroke Data for that character until the stop command and sum all bytes including stop command itself.
  4. Use result value as Font Size.
  5. Do not foget that this Font Size must not include Font Header size.

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 Must be 0x2B. Indicates this is a stroked font.
INT16LE Character Count Number of total characters stored in this font.
char Undefined Borland's own documentation labels this as "undefined".
char 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 relative from the start of this header.
INT8 Scan Flag ! Borland's documentation calls this the "scan flag", but doesn't explain what it is for ("set to 1 if set is scanable").
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). Ignored, should be 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.
char[4] Font Name Borland's own documentation says "four character name of font", but it's always all zero.
char Undefined Borland's own documentation labels this as "undefined".
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 
^ ^ ^
| | +----------- 6-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. Borland font rendering code silently ignores this - checked in Turbo Pascal 7.1 (1997) and in Turbo C 2.01 (1988). ! 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 started at 0 with Y-axis inverted to screen axis, in other words negative values goes below baseline and positive above. See the diagram to the right.

Source Code

Draw Stroke Data

/*
Copyright 2024 CTPAX-X Team
This source code licensed under the Apache License, Version 2.0 (the "License"):
https://www.apache.org/licenses/LICENSE-2.0
*/

/* draws a character:
x, y - initial screen positon
b - buffer pointer to a Stroke Data for character */
void chr_draw_char(int x, int y, unsigned char *b) {
char m, dx, dy;
int xc, yc;
  /* initial position according to Stroke Header */
  y += origin_to_ascender - origin_to_descender;
  xc = x;
  yc = y;
  moveto(xc, yc);
  do {
    /* get mask */
    m = b[0] >> 7;
    m += m + (b[1] >> 7);
    /* 0 - stop; 1 - scan; 2 - move; 3 - draw */
    if (m > 1) {
      /* bit arithmetic (note dx/dy signed 8 bit type):
      if sign bit is set - expand to full signed value */
      dx = (b[0] & 0x7F) | ((b[0] & 0x40) << 1);
      dy = (b[1] & 0x7F) | ((b[1] & 0x40) << 1);
      dy = -dy;
      /* dx/dy relative to initial screen positon */
      xc = x + dx;
      yc = y + dy;
      if (m == 2) {
        moveto(xc, yc);
      } else {
        lineto(xc, yc);
      }
    }
    /* next byte pair */
    b += 2;
  } while (m);
}

Shape Viewer

This FreeBASIC program will draw all of the characters in the specified font, and give details about the font.

ScreenRes 800, 600, 4

' 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

XBuffer = 40
YBuffer = 32

For I = StartingChar To (StartingChar + CharacterCount - 1)
	' Jump to the starting offset of this character.
	Get #1, CharacterDefinitionOffset(I) + StrokeDefinitionOffset + HeaderSize, Buffer

	' Character position in 16x16 table.
	XStart = 80 + (I Mod 16) * XBuffer + ((XBuffer - CharacterWidths(I)) \ 2)
	YStart = 70 + ((I \ 16) * YBuffer) + CharacterHeight

	' Draw character bounding box.
	Line(XStart, YStart - OriginToCapital)-(XStart + CharacterWidths(I), YStart - OriginToDescender), 8, B

	' 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 bits.
		XOpcode = Bit(XByte, 7)
		YOpcode = Bit(YByte, 7)
		' Clear the opcode bits.
		XByte = BitReset(XByte, 7)
		YByte = BitReset(YByte, 7)
		' Set the values to negative if the signed bits are on.
		If Bit(XByte, 6) = True Then
			XByte = BitSet(XByte, 7)
		End If
		If Bit(YByte, 6) = True Then
			YByte = BitSet(YByte, 7)
		End If

		XNew = XByte
		YNew = YByte

		' Normalize the Y position.
		YNew = -YNew

		If XOpcode = 0 Then
			If YOpcode = 0 Then
				' End of character definition. Move to the next character.
				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), 15
			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