The GD library enables PHP the to load custom fonts (with imageloadfont()) and add text to images. But it can also be leveraged to create interesting effects — particularly when one uses PHP to first create a custom font.

In order to be able to create a custom font we need to find out more details about its format but fortunately we don't have to look far as the PHP manual page on imageloadfont() explains the layout in "Table 1". The information that is contained in the font file is the number of characters in the font, the ASCII value of the first character in the font, pixel width of each character, pixel height of each character, followed by the data describing the actual characters, one byte per fixel per character. I recommend you look at the table which breaks down the information nicely. The GD library web site describes the actual C data structure.

The next step is to generate the font file containing data according to these specifications. A relatively easy way to do this is using PHP's pack() function.

So let's get started. Here's a short PHP script that will create the header, characters and write all data into the "myfont.fnt" file. (If you're trying this for yourself on a Linux server, remember to check file permissions, so that PHP is able to actually write the file.)

$char_head =
'01000000'. // only one char
'20000000'. // space (0x20) is first char
'08000000'. // 8 pixels wide
'08000000'; // 8 pixels high$char_data =
'FFFF0000000000FF'.
'0000FF000000FF00'.
'0000FF000000FF00'.
'0000F0000000FF00'.
'0000FF000000FF00'.
'0000FF000000FF00'.
'0000FF000000FF00'.
'000000FFFFFF0000';

$fontdata = pack('H*', $char_head . $char_data);

// write the font data to file
$file = fopen('myfont.fnt', 'w');
fwrite($file, $fontdata);
fclose($file);

Now we just need to put the new font to use. The following script simply loads the font, creates an image 24 x 8 pixels small, writes the "space" character 4 times in blue on gray background, and sends it to the web browser.

// load the font
$myfont = imageLoadFont('myfont.fnt');// create the image canvas (4 characters wide)
$image = imageCreate( 8*4, 8 );

// allocate the colors (first one is background)
$white = ImageColorAllocate($image, hexdec('cc'), hexdec('cc'), hexdec('cc'));
$blue = ImageColorAllocate($image, hexdec('33'), hexdec('66'), hexdec('99'));

// write the string (four spaces)
imageString($image, $myfont, 0, 0, '    ', $blue);

// send image to browser
header('Content-type: image/png');
imagePNG($image);

However, there's always at least one caveat, and the bad news is right in the PHP manual:

The font file format is currently binary and architecture dependent. This means you should generate the font files on the same type of CPU as the machine you are running PHP on.

Thus, the issue at hand is whether to write the data with big, or little endian byte order. Currently, the code snippet above uses the little endian format and that works just fine on, say, my PC. But that may not be true for other computer architectures.

My plan for this article was to try and demonstrate how when the $char_head format is changed, the font no longer works. However, while doing this I ran across an interesting feature, which looks like a half-implemented endianess detection routine for cross-platform compatibility. For instance, GD gives an error when I convert just the first line into big endian. On the other hand, it works again when the width and height integers are adjusted as well. Note, however, that the second integer must remain in little endian format. This threw me off, and I had to investigate. Turns out that in the PHP code (specifically, ext/gd/gd.c in the definition of PHP_FUNCTION(imageloadfont)) there's a sanity check embedded. Essentially PHP compares the character count in the header with the actual filesize. If this doesn't match, it'll simply flip endians. Due to neglecting to flip endians of the offset (first character in font), this check does not guarantee architecture independency and technically should be filed as a bug on PHP's bug tracker.

However, In order to guarantee the requirement that the created font will function on the machine it was created on, we should make some modifications and use the following method instead:

$char_count = 1; // only one char
$char_first = ' '; // space is the first char
$char_width = 8;
$char_height = 8;$char_data =
'FFFF0000000000FF'.
'0000FF000000FF00'.
'0000FF000000FF00'.
'0000F0000000FF00'.
'0000FF000000FF00'.
'0000FF000000FF00'.
'0000FF000000FF00'.
'000000FFFFFF0000';

// convert to binary data
$fontdata = pack('llllH*',
$char_count,
ord($char_first),
$char_width,
$char_height,
$char_data);

// write the font data to file
$file = fopen('myfont.fnt', 'w');
fwrite($file, $fontdata);
fclose($file);

That's already it. The resulting image is this:

There are plenty of uses for this: creating dynamic icons on the fly, creating colorful patterns around text, creating maps for web-based games just off the top of my head.

This post has no comment. Add your own.

Post a comment