Dynamic bitmap fonts

Submitted by davidc on Tue, 14/04/2009 - 16:48

For the recent April Fool's joke at goofans.com, I wanted to exactly reproduce the look and feel of the in-game computer in World of Goo. This included reproducing the font used with some clever CSS/DHTML. This article describes a reusable, efficient, client-side DHTML bitmapped font library.

Since I put in a tremendous amount of work for an April Fool's prank, I decided to publish this code here in case anyone else has a need for bitmapped fonts. It is released under the MIT license.

The other options

Reproducing the font in HTML/CSS would have been impossible. Unfortunately this font is not one that many people would have installed, plus it has an outline around it that is impossible to reproduce without any control over the text outline width.

Generating images of the text server-side would be a possibility, but with around 120 possible lines of text would have involved a considerable amount of wasted bandwidth and increased load times for the user. Furthermore, PHP doesn't have any way to set stroke width either, which would have required either slow hacks (draw image at numerous offsets in a circle around the intended position) or using the source image anyway.

PopCap font format

Fortunately the game uses fonts generated using the PopCap framework. The game is distributed with two files per font: an image containing all the characters, and a machine-readable description.

I decided to use the following approach: The client would download the "large" (~40k) image of the font just once. I would then create individual characters using appropriately sized DIVs, each of which had this image as the background, but with the background offset according to the character required.

How it works

Each character in the text to be drawn is looked up in the CharList to find its offset. Using this, the width from WidthList and the position in the PNG from RectList are found. A small DIV is created for that character, and the background-position is made negative since we want the offset to which the background should be moved.

Each character in a word is individually positioned, and kerning pairs are also checked and the position adjusted accordingly. The whole word is then wrapped in a DIV whose width equals the word's width, to prevent wrapping within the word. The word then has a margin-right set to the word spacing.

The word DIVs use display: inline-block to get them to wrap appropriately. Since Internet Explorer does not support this, a small CSS hack of zoom: 1; *display: inline; is also used.

For each font, CSS selectors are created: the class for the letters, the class for the words, and (optionally) the class for the hovered text. This is to minimise the amount of actual CSS that is styled directly to each character DIV - which is now limited to just positioning.

Preparing the font definition

To prepare the font, you need to convert the .txt file describing the font into a format that JavaScript can read. I decided to keep almost the exact same structure as the PopCap font file, so copying the .txt file to .js, simply make the following changes:

  1. First wrap the whole file in an object definition. So add to the top (replacing TwCen18 with your own variable name):
    var TwCen18 = new BitmapFont({

    and to the bottom:

      });
  2. Javascript uses [] to surround literal arrays instead of (), so globally replace ( with [, and ) with ]. It uses commas to separate fields, so globally replace ; with ,.
  3. Get rid of all the definitions you don't need. The only Defines you need to keep are CharList, WidthList, RectList, OffsetList, KerningPairs, and KerningValues.
  4. Lines like "Define CharList" will instead become object fields. So replace Define CharList with CharList: (note trailing colon).
  5. You need to add some additional fields. Image is the font image file, ImageOver is an optional "hover" image file, and SpaceWidth is an integer for the width of a space (which you will find in the original .txt file under LayerSetCharWidths). LineHeight is also needed, as some control is required over this to avoid descending/ascending letters causing weird line spacing.
  6. Note that JavaScript doesn't allow trailing commas after the last item in an array or object definition, so remove any superfluous commas.

Once you've amended this file you should end up with something like TwCenMTCondensedExtraBold18.js.

Drawing text

The code uses jQuery, since I already used it elsewhere on my site. However the jQuery dependency is minimal. If you don't use it, simply search for lines containing "$" and replace them with their DOM equivalents (document.createElement, element.appendChild, element.style.x = y).

In your HTML page, add the necessary includes to the HEAD, noting that BitmapFont.js must come first:

<script type="text/javascript" src="BitmapFont.js"></script>
<script type="text/javascript" src="TwCenMTCondensedExtraBold18.js"></script>

Then, whenever necessary, make calls to <font>.drawText(<string>). This function returns a jQuery object that you can simply .appendTo() your DIV. For example:

    TwCen18.drawText("Are you my MOM?").appendTo('#question');

You can also add the "hoverable" class to cause BitmapFont to switch to the ImageOver image when it is hovered. This is used in the MOM sample to make the options green when you mouseover them.

    var option = TwCen18.drawText(momDialogue[i][momTrainState[i]][0]).appendTo('#textBarMiddle');
    option.addClass('hoverable');
    option.css('width', '100%');
    option.click(selectOptionFunction(i));

Download

BitmapFont.js (SVN revision 130)

Samples

TwCenMTCondensedExtraBold18.png.
TwCenMTCondensedExtraBold18.txt (PopCap file, before conversion).
TwCenMTCondensedExtraBold18.js.

Demo

MOM, over at goofans.com.

Enjoy!