This month we will continue our look at printing characters to a bitmapped display. Last month we looked at a method which transferred a character dot pattern to the bitmapped display. This month we will look at a second method, which draws the characters.
Printing Bit By Bit
With the appropriate set of line segments, virtually any character shape can be drawn. The characters do not necessarily have to look like the standard ASCII character set. In addition, you are not restricted to a fixed character cell. Each character can be as complex and as large as you like. For this flexibility, you do lose a few advantages offered by the use of character dot patterns. (It becomes a little more difficult to print in reverse video and will likely take a little longer to print the character when characters are drawn rather than transferred.)
With the drawing method, we will need to make use of a line-drawing routine. For convenience, I will be using the machine language line-drawing routines presented in the May issue of COMPUTE!. However, for use in the example BASIC program which follows, almost any line-drawing routine will suffice. (The one found in COMPUTE!'s earlier "SuperBASIC 64" program could be used if you desire. Some minor modifications to the BASIC program will be required, though.)
To draw a given character in the bitmapped display, we will need some data to define how the character should be drawn. Unlike the transfer method, where the format for such data is already fixed, here we have total freedom to define our own format. The format must specify what line segments should be drawn to form the characters. This means that the data must define the starting and ending coordinates of each line segment. Another thing to note is that the data will need to define these coordinates relative to the previous coordinates. By specifying the next point based on the previous point, the character can be drawn anywhere in the bitmapped display.
To simplify the following discussion, I will use the term "vector" to refer to the line segments which make up a character. Also, I will use the term "vector string" for the data which defines how to draw a character.
One way we could define the format of the data in the vector string is to specify each vector with two pairs of relative coordinates. A single byte could be used for each relative coordinate, which could represent a value from 127 to –128. Thus, four bytes would be required for each vector in the vector string.
As I mentioned in the May column, I prefer to have the draw function continue from each previous endpoint. This eliminates the need to specify a new beginning point every time. The catch is that there must be some way of moving the last endpoint without drawing. Assuming we define a way of moving the endpoint for our vector string, then it will be possible to specify a vector using one pair of relative coordinates, rather than two. For this to be an advantage, a fair percentage of the vectors would need to draw from the end of the previous vector. When creating characters from vectors, I believe this will generally be true.
If the characters are not going to be that large, there is another phenomenon: Most of the vectors will be fairly short. Assuming they are typically short enough, we could save more bytes by using one byte to specify a vector. The byte could be split into an upper and lower four bits, with each half able to represent a relative coordinate of 7 to –8. This may not seem like very much, but if the vector isn't too long to be represented by two of these bytes, we haven't lost anything.
This isn't the only way to use a single byte to specify a vector. The byte could be split into two parts so that one part specifies a direction and the other a distance. The direction in this case would most likely be a multiple of 45 degrees. This actually works quite well for drawing characters. However, I will go with the format of putting relative coordinates into the byte. I will refer to such bytes as "vector bytes" in the discussion which follows.
Given that the vector has a limited range, we will need to define some way of invoking exceptions to handle the times when the range is exceeded. Also, we still need to define a way of moving instead of drawing, which we will also treat as an exception. One way to do this is to use one of the coordinate values to signal the exception. The other relative coordinate could be used to indicate which exception. Since this uses both halves of the vector byte, the exceptions will require additional bytes.
Now we are ready to get down to specifics. Let's try putting the relative coordinate for X in the upper half of the vector byte. Naturally that means putting the relative coordinate for Y in the lower half. As for a value to signal exceptions, it is most logical to choose a value at an extreme. Since our range is from 7 to—8,—8 would be the best choice. It also would be best to have this value in the upper half of the vector byte. This would cause the exception bytes to fall in the range of 128 to 143. Bytes outside this range will be regular "drawing" vector bytes.
There are four exceptions we will need to deal with initially. These exceptions are for signaling a move, an extended draw, an extended move, and the end of the vector byte string. With the upper half signaling an exception, this leaves the lower half to flag the exceptions.
Also, the numbering of the exceptions will be a little easier if we treat the four bits as unsigned rather than signed. This lets us have values from 0 to 15, instead of 7 to—8. For the exceptions, let's try values of 0, 1, and 2 to select move, extended draw, and extended move, respectively. To mark the end of a vector byte string, let's try 15, to choose an extreme again. The following table summarizes these choices:
Data Formats For Vector Byte String Byte Bit No. 76543210 Vector Byte 1 1 DX 1 DY I DX = 7 to-7 DY = 7 to-8 Move Exception 1 I -8 I 0 I =128 2 I DX I DY I DX, DY = 7to -8 Extended Draw Exception 1 I -8 1 I I = 129 2 I DX I I = 127 to -128 3 I DY I Extended Move Exception 1 I -8 I 2 I = 130 2 I DX I = 127 to -128 3 I DY I = 127 to -128
Drawing Strings Of Characters
Now we are ready to implement the above in a BASIC program. The result is shown in Program 1. The task of drawing the character has been split into a number of routines. One routine fetches the next vector byte, and another unpacks the relative distances. There is also a separate routine to handle vector byte draws, vector byte moves, extended draws, and extended moves. Finally, there is a routine which draws a character, and which in turn uses that routine to draw a string of characters.
The vector byte data included in Program 1 defines vector byte strings for characters 65 through 90, or A through Z. The vector byte strings will draw the ASCII character corresponding to the character code. The space character is also defined. The vector byte strings are stored in the string array VB$ and are accessed by using the character code as the subscript into the array. Prior to running the program, it will be necessary to load the line-drawing routines presented in the May column.
I have included a routine which lets you specify a character rotation in increments of 90 degrees. For drawn characters, the rotation involves simply negating or swapping the relative coordinates specified in a vector byte. Rotating the transferred character pattern is not too difficult provided the cell is square, as it is in our case. Rotating the character to other angles typically won't produce desirable-looking characters, and may be too complex to implement.
The following is a table showing the routines that are available, and what their start address is. define the base location of the second jump table. Here is a list of the routines: Since this will be the second jump table (to complement the line-drawing jump table), I use J2 to
|J2 + 0||SET PUT CHAR. DATA LOCATION|
|J2 + 3||PUT CHARS.IN BITMAP (TRANSFER METHOD)|
|J2 + 6||SET DRAW CHAR. DATA LOCATION|
|J2 + 9||DRAW CHARS.IN BITMAP|
|J2 + 12||SET ROTATION|
|J2 + 15||NOT USED YET|
|J2 + 18||NOT USED YET|
|J2 + 21||NOT USED YET|
The jump vector location of these routines is shown as the variable J2 plus an offset. To obtain the actual address, J2 should be set to the base of the jump table which is 50176 or C400 hex. The following list gives the syntax for using each of the defined routines in the jump table:
|SYS||J2 + 3, CHAR or STRING|
|SYS||J2 + 6, LOC|
|SYS||J2 + 9, CHAR or STRING|
|SYS||J2 + 12, ROT|
|ROT : 0 = NO ROT., 1 = 90 DEGREES 2 = 180 DEG., 3 = 270 DEG.|
Both the put character (J2 + 3) and draw character (J2 + 9) will accept either a single character or a string of characters as an argument. If the argument supplied is a numeric value, it will be interpreted as the ASCII value of a single character. If the argument is a string, the entire string will be printed.
The location required by the put character routine should be the base address of the character dot patterns to use. The location required by the draw character routine is the base address of a 256-byte table containing pointers to 128 vector byte strings. The pointers to the vector byte strings are each two bytes, stored as low byte followed by high byte. Use of a table is necessary because the length of each string may vary, making it impossible to calculate the locations of the vector byte strings directly.
Program 2 will POKE the machine code for the character routines into the proper locations. Like the program which POKEs the line-drawing routines, that last number in each data line is the sum of the previous eight bytes on the line. Provided you don't make two errors which cancel each other, the program will report any lines that have mistakes in them. If there are no detected errors, a SUCCESSFUL LOAD is reported.
Program 3 provides a simple illustration of the use of the character routines. For vector byte data, add the DATA statements shown in Program 1, which will define ASCII characters A through Z, and space. The vector byte data will be placed at the top of BASIC'S free RAM, after 1024 bytes of space is reserved from BASIC. You will be able to see the increase in speed over the BASIC routines.