I was talking with Jeremy in the lab today, and we got talking about hardware support for proportional fonts on the C65GS. I had thought about doing this before, but had put it on the back-burner for a while, because I hadn't really come up with an elegant solution.
While we were talking today, however, we got talking about how I had 2 spare bits in the character number in 16-bit text mode, where two screen RAM bytes are used for each character. I don't remember exactly who came up with which part of it, but by the end of it, we had come up with a workable solution not only for hardware support for proportional fonts, but anti-aliased fonts, too (more on that in a future post).
Proportional fonts really just requires specifying the width of each character, so that some can be narrower than others. With two spare bits, it was easy enough to allow characters to be 8, 6, 4 or 2 pixels wide. Characters more than 8 pixels wide can then be constructed with any number of 8 pixel wide characters, and one narrow character to make it easy to obtain any even number of pixels in width. It would be nice to allow odd widths, but it feels like it is a reasonable trade-off to have to round to the nearest 2 pixels.
Similarly, tall fonts can be constructed with multiple rows of text. For fonts not a multiple of 8 pixels high, a raster split could be used to skip one or more rows of pixels.
The result is conceptually very simple. The main trade-off is that skinny characters still use the same amount of RAM as full-width ones, but that seems a reasonable trade-off. In fact, it was so simple that I was able to implement it in about an hour, and the main functionality worked first time, as can be seen below:
Each row has a different width specified for the "A" at the beginning of the line.
To use this feature, you first have to enable 16-bit text mode, where two bytes of screen memory describe each character on screen. This is done by setting bit 0 in $D054.
In terms of screen RAM, the memory for the rows here looks something like:
0400 01 00 02 00 03 00 04 00 05 00
0428 01 40 02 00 03 00 04 00 05 00
0450 01 80 02 00 03 00 04 00 05 00
0447 01 c0 02 00 03 00 04 00 05 00
The 01, 02 ... 05 is the character numbers for A through E, and these stay the same. For all but the A's, the 2nd byte is 00 to indicate that no special attributes are set for the characters B through E. However, the high-byte for the A characters is modified in each row to have all possible combinations in bits 6 and 7: the higher the value, the narrowerer the character.
While I am fiddling with text attributes, I should explain what the other bits in the high byte mean:
bits 0 - 3 = bits 11 - 8 of the character number. i.e., there can now be 4,096 characters in a character set.
bit 4 = flip character horizontally
bit 5 = flip character vertically
The following screen shot shows the flip bits in action:
The contents of screen RAM is:
0400 01 00 02 10 03 20 04 30 05 00
The ability to flip characters is designed to be used with full-colour text mode, where (some or all) characters on the screen consist of 64 8-bit pixels, providing a graphics mode that can be quickly scrolled.
Flipping characters in such a mode allows 64-byte characters to be reused in a graphical display without too much obvious repetition, e.g., for in textures in games.
Combining this with variable width characters introduces even more opportunity to reuse characters, and thus allow more interesting and complex high-resolution graphics within the limits of the 128KB of chipram.