Mini Fonts

By Chris Dewhurst

Originally published in EUG #64

Normally your BBC or Electron displays characters of 8 x 8 pixels whose definitions, or patterns, are stored in eight bytes. Data for the complete ASCII character set occupies three pages - 768 bytes - of memory, stored from &C000 onwards in the Operating System ROM. In this articles we are going to see how to squeeze data for a character set into just 111 bytes.

You may have noticed that numerous commercial arcade games make use of characters that measure 5 x 7, 3 x 7 or sometimes 3 x 5 pixels. These can look attractive in Modes 2 or 5 where 5 x 7 pixel letters overcome the 'squashed' appearance of the default operating system characters. Furthermore, 26 of these characters would fit across a Mode 2 display instead of the usual 20. We could define some 5 x 7 pixel letters on an 8 x 8 grid as VDU characters, then print them at the graphics cursor in BASIC, moving back a few graphics points after each to close up the gap in between.

But there is a smarter way. What we do is store the patterns rotated degrees clockwise and, in machine code, examine the bits and write corresponding pixels to the screen RAM, working from the bottom of the character pattern upwards then across:

1. Normal Character 2. Rotated Character
Definition (5x7 pixels): Definition:
 
.***.... ..*****.
*...*... .*.....*
*....... .*.....*
*....... .*.....*
*....... ..*...*.
*...*...  
.***....  
........  

You will also see that only five bytes are used to store the chararacter pattern instead of eight. Most games use ten digits, a set of capital letters and a space, in which case the character set would take up 5 * 37 = 185 bytes. A set of 3 x 7 pixel characters would take even less memory: 3 * 37 = 111 bytes.

Now let's introduce the demonstration program, MINIFN. A font data file is loaded in which comprises several typefaces based on a 5 x 7 and 3 x 7 grid:

Typeface Description Example of use in commerical games
 
5 x 7 family:
0 Sans Serif Pipemania, PLAY IT AGAIN SAM menus
1 Bold 0 CRAZY TRACER
2 Digital GALAFORCE, PALACE OF MAGIC
3 LCD My own design
4 Digital 2 My own design
5 Digital 3 JOE BLADE
6 5x5 Another of my own designs
 
3 x 7 family:
7 Sans Serif Used in lots of Mode 2 and 5 games with half-sized characters, e.g. Repton, Citadel 2, Xor, Audiogenic games. Sets 9 and 10 have the horizontal strokes thickened to look better in Mode 2 or 5
8 Variation on 7
9 Variation on 7
10 Ransack
11 3x5

For each typeface (0-11), Mode (0-6) and text height (single or double), the machine code is assembled and some sample text is printed on the screen. Press a key to cycle through all the variations - there are 168 in all!

The machine code isn't the same as the way it's done in the games mentioned above because it works in any mode including text modes 3 and 6. It is also more efficient than commercial examples I've disassembled.

The text is stored thus:

Offset Description
0-1 Screen address LSB, MSB
2 Text length
3 Text colour (mask)
4 onwards Text. It doesn't consist of ASCII codes. Instead, "0" to "9" have value 0-9, "A" to "Z" have values 10 to 35 inclusive, and space value 36.

Function FNmssg(x%,y%,m%,a$) automatically formats a$, the text string, at position (x%,y%) where x%=columns across and y%=character runs down, screen.

FNmssg uses the information READ in from lines 1070-1130 for the current Mode. H% is the address of the top of the screen, and bpc% is the bytes per character row. m% is the byte mask (colour). We will now look at the assembly language:

Zero Page Variables

&70/1 Address of message data
&72/3 Screen address, copy of bytes 0 and 1 in above table
&74 Text length, copy of byte 2 above
&75 Mask
&76 Working copy of pattern byte
&77 Pixel value
&78 Column count: 5 for 5 x 7 characters, 3 for 3 x 7 characters
&79 Temporary copy of Y register
&7A Height flag: 0 for single height, <>0 for double height. MESS2 is called with Y=0 to plot bottom half of character, Y<>0 top half.

Lines 320-380. The MESSAGE routine is entered with Y=message number times two, e.g. 0 for the first message, 2 for the second message, 4 for the third, etc. The screen address, colour and length of the message is copied into &72-&75.

Lines 400-460. If text is double height, conditional assembly adds a label, MESS2, for the code to plot half of the character. The screen address and message length is recopied with the bytes per character line subtracted from the original address and MESS2 is reached with Y<>0.

Line 470-480. Store left hand pixel value, store column count.

Lines 490-530. Get next character from message and calculate pattern address - char*5 or char*3.

Line 540. Store text pointer.

Line 550. There must be a gap between one character and the next, so if column count=0 then skip section which writes pixels to screen - thus creating a blank column of pixels.

Lines 560-590. Get pattern byte. If plotting bottom half of character in double-height mode then discard top four bits by ASLing them out.

Lines 600-690. Shift out the eight bits one by one and loop till one column of eight pixels is built up built in the screen RAM.

Line 700. The leftmost pixel of the highest colour number in a column is 128 for Modes 0,3,4,6; 136 for Modes 2 and 5; 170 for Mode 2. This is masked to give the right colour when the pixel is EORed to the screen. In all cases, the value of the next pixel to the right is always obtained by dividing this by two, and so on for as many pixels as there are across a column - 8 for 2-colour modes, 4 for 4-colour modes, 2 for two-colour modes. C=1 after the rightmost pixel has been reached (it is always an odd number and doing LSR will shift the 1 into the Carry).

Lines 710-730. Add 8 to screen pointer to get next column of bytes and reset pixel value to leftmost value.

Line 740. Increment X register for next pattern byte and loop till all pixel columns plotted.

Line 750-760. Retrieve text pointer and loop till all characters plotted.

If you want to use the miniature fonts in your own programs you will not need all of the assembly language. Study PROCassem and see what you can get rid of. For example, remove lines 390-460, 510-520, 570-580, 640-650 and 670-680 if you only require single height fonts. Substitute numbers for variables, e.g. &3000 for H%, &280 for bpc% in FNmssg().

Presently the machine code can cope with only one typeface so pick out the desired font with the assistance of PROCexport(S%,F$). S% is the typeface (see table above), F$ is the output file. The required command will be given so select the drive numbers and any other star commands you want, then copy what was printed.

Designing Your Own Fonts

The program Convert will convert (rotate) characters designed in any ASCII character editor capable of saving one block of characters. Enter the filename of the design for digits "0" to "9" and then the name of the file containing the patterns for "A" to "Z". Enter the name of the file under which the converted characters are to be saved. Note that Convert automatically appends a blank character to the end of the set, based on the value of clmns% - the number of columns of pixels in a character (i.e. the width of a letter in pixels).

Improvements

Of course, you could add punctuation marks and other shapes. Alter the value of nchars% in line 110 of Convert. (clmns% in the previous line should be 3 for 3 x 7 pixel fonts.) Regarding Minifnt, allow lh% to be stored in with the text data so that letters can be positioned with pixel accuracy. Use other values for the mask for stripey text, or toggle it within the machine code routine for a 'dithered' effect.

Christopher Dewhurst, EUG #64