.glkfont File Format
Since .PCX may not be delivering quite as good a compression as possible, in our test example coming out to about 30k (8 bit .gif being about the same and 8 bit .png being 28k) I decided that like glFont, glKernedFont needed it's own image format. Since the most common values are 0 and 255 and those make the longest runs, that's where encoding efforts should be put.
Before diving into encoding though, I figured it should store some simple values in the header, and while at it, we can put our kerning table in there too.
tglKernedHeader=record formatRevision, bufferWidth,bufferHeight, charWidth,charHeight:dword; kerningTable:tglKernedTable; reserved:array[0..127] of byte; end;
ALWAYS reserve some extra space in your header for future expansion - you never know when you might want to add extra information.
That leaves just making an encoding method for the images. Since values 0 and 255 are the ones likely to occur more than once in a row, we can just detect if a 0 or 255 is in the datastream, repeat that value by however much the next byte contains. This simple RLE encode means we don't even have to play with the greyscale pixels, and with how most fonts are laid out we get a compression rate hovering around 10:1, marginally better than either .png or .pcx deliver - with a heck of a lot less code involved in decoding it. Our example font compresses down to only 24k of data, with the header information bringint it up to 25 and a half k compared to the 2.7k .kerning text file and 30k PCX. about a 20% improvement for a decoder that only needs a handful of lines of code to work.
This format had one unexpected benefit... most of the time when you use a format like JPEG or PNG and then put it into a compressed archive like .rar or .zip, you don't get any more compression out of it. Even PCX doesn't really encode all that great - Now, I don't know if this is going to be typical of all .glkfont files, but they seem to compress an additional 50% or so - In a future version of this file format I may add some form of LZW or other compression to take advantage of this. If I can get 240k of uncompressed data down to 12k, I'll certainly make a go of it.
Now that we have our data format, we need to read it in and do something with it. We just open the file, read the header moving the data to glFloat, read in our kerning table, then unpack the pixel data to a buffer. The decode routine is pretty simple - first we allocate enough room for our buffer, because we want to use the data in our file as a alpha channel, we will use the 16 bit gl_luminance_alpha format so we need twice as many bytes as we have pixels.
Then I go and make even more pointers, one pointing at our target buffer, one pointing at my file buffer, and one pointing at the end of the target buffer. Using a end pointer and comparing against that prevents target buffer overruns if the file gets corrupted, and means we don't need an extra counter variable.
while not(fpoint>=fBufferEnd) do begin case fPoint^ of 0,255:begin b:=$FF and (fPoint^ shl 8); inc(fPoint); t:=fPoint^; while (t>0) do begin pPoint^:=b; inc(pPoint); dec(t); end; end; else begin pPoint^:=$FF and (fPoint^ shl 8); inc(pPoint); end; end; inc(fPoint); end;
Look Ma!!! No counters! In assembly using CX as a counter is a good idea, in a high level language it's just wasted logic. Pretty simple, check if the current fPoint^ value is 0 or 255, if so repeat 0 or 255 by the value in the next byte. Otherwise, just copy the byte over. When putting our value into our ^Word buffer we shift it left to put it into our high byte, and turn on all low byte values. I use and instead of add mostly out of old-school habit, back when the assembly operation 'and' was faster than 'add', especially of a fixed value.
From there it's a simple matter of freeing the file buffer, binding the pixel buffer to a texture, and releasing the pixel buffer.