Westwood Font Format v3

From ModdingWiki
Jump to navigation Jump to search
Westwood Font Format v3
Cc vcr.png
Format typeFont
Max glyph count256
Minimum glyph size (pixels)0×0
Maximum glyph size (pixels)255×255
Access modeIndexed
Metadata?None
Bitmap glyphs?Yes
Vector glyphs?No
Compressed glyphs?No
Hidden data?Yes
Games

This is the third type of bitmap font created by Westwood Studios, used in the Lands of Lore and Legend of Kyrandia series and in their early RTS games. It is a 4-bit-per-pixel font with a variable amount of characters, which allows the separate symbols to specify their width, height and Y-offset.

File format

Since the source code of Command & Conquer and Red Alert was released, the full handling of these fonts is now available. It can be looked into here, in the files FONT.H and FONT.CPP.

Header

The font format starts with the following header.

Offset Data type Name Description
0x00 UINT16LE Size File size.
0x02 BYTE CompMethod Compression method. Always 0x00 for this type. This evolved into a format indicator, since v4 uses value 0x02 to indicate 8-bit fonts.
0x03 BYTE NumBlks Number of blocks. Technically indicates the number of offsets that follow in the header, but in reality, this must always be 0x05 for the font to be seen as valid.
0x04 UINT16LE InfoBlk Indicates the offset of the _FontInfo data. Normally always 0x0E; right after the main header.
0x06 UINT16LE OffsetBlk Offset of the array of data offsets.
0x08 UINT16LE WidthBlk Offset of the array of symbol widths
0x0A UINT16LE DataBlk Start address of the font data. Unused in v3, since the addresses in the OffsetBlk array are absolute. In v4, offsets are relative to this value.
0x0C UINT16LE HeightBlk Offset of the array containing the symbol heights and Y-offsets.

This is normally immediately followed by the _FontInfo block, as specified by the InfoBlk offset. The games only seem to use MaxHeight and MaxWidth from this block.

The _FontInfo block has the following structure. Note that in the Red Alert 1 source code, its first four bytes are taken together as a single int32 named "huh", which seems to indicate they lost the information on what exactly this area contains. This means that the only reliable way to determine the amount of characters is through reading the index arrays and seeing where they need to end to avoid overlapping with the other addressed data.

Offset Data type Name Description
0x00 BYTE Unknown Unknown. Always 0x12.
0x01 BYTE Unknown Unknown. Always 0x10.
0x02 BYTE Unknown Unknown. Always 0x00.
0x03 BYTE NrOfChars Number of characters. Actually, this is the byte value of the last character in the list, so the real number of characters is this value plus one. As mentioned, this is not actually used, and thus not a reliable way to get this data.
0x04 BYTE MaxHeight Overall font symbols maximum height, in pixels.
0x05 BYTE MaxWidth Overall font symbols maximum width, in pixels.

Following this header are the referenced arrays and the font data, normally in this order:

  • Array of UINT16LE values indicating the offset of the data for each symbol, relative to the start of the file. The start offset of this list is specified by OffsetBlk.
  • Array of byte values indicating the symbol widths, in pixels. The start offset of this list is specified by WidthBlk.
  • Array containing two bytes per entry; the first being the Y-offset, the second the height in pixels. The start offset of this array is specified by HeightBlk.
  • Actual symbol data referenced in the offsets list.

However, since the game just follows the offsets as it reads them, this order probably doesn't really matter.

The fonts could technically be pushed slightly beyond their normal limit of 65535 bytes, by making sure the last symbol offset referred to in the index is still below that maximum. Though there is no guarantee that the game will reserve enough space to fully read such large font files into memory, and third party apps to view or edit the font may have trouble identifying such files, since the file size saved in the header will always be incorrect.

Optimisation

The font data is uncompressed, but it is optimised by making the offsets for any identical symbols in the font refer to the same data. The use of this technique means that very little space is lost by including the normally unusable symbols range of 0 to 31. Most of these indices are filled with the same one or two filler symbols, which will each only be saved once despite being used dozens of times. This also allows fonts with no difference in upper and lower case to expose the same image data for both, with barely any increase in file size.

This optimisation technique can actually be used on any type with indexed frames, in any game, as long as the game doesn't do any elaborate checks on the expected data length.

Image data

The actual font data is 4 bit per pixel, and always uses the minimum stride per line, so, for example, a font with a width of 5 will require 3 bytes to contain all five nybbles, and, as is common for image formats in general, will have these three bytes for each line, without optimising a next line's start-nybble into the unused end of the previous line's byte. The general method of calculating the minimum stride for any bit-per-pixel value under 8 is (width * bpp + 7) / 8.

For each byte, the lowest nybble is the leftmost of the two pixels, which might seem counterintuitive when compared to the way the bytes are viewed in a hex editor.

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
Westwood Font Editor WindowsYesYesYesNoN/A Automatically trims off the top of the characters on save, and saves the trimmed amount as Y-offset.
Engie File Converter WindowsYesYesYesNoN/A Automatically trims off the top of the characters on save, and saves the trimmed amount as Y-offset. This font type was added to Engie to allow creation of font sheets.