Displaying Text ~~~~~~~~~~~~~~~ So far all the graphics displayed on the screen have been designed using an external package. No modifications, improvisations or manipulations have been implemented. It's time to broaden the horizon! This chapter deals solely with displaying 8 pixel by 8 line fonts, commonly called an 8x8 font. Since the width of each character is 8 pixels ( or 1 byte ) horizontal resolution will be restricted to the nearest byte. This eliminates the need for complicated shifting, but still allows some impressive results. ASCII Representation ~~~~~~~~~~~~~~~~~~~~ Though we recognise characters by their shape, computers need another means. Over the years the ASCII standard has been adopted. The ASCII standard assigns a byte value to every printable character as detailed in the table below. second nibble 0 1 2 3 4 5 6 7 8 9 A B C D E F ------------------------------------ 2 | spc ! " # $ % & ' ( ) * + , - . / 1st 3 | 0 1 2 3 4 5 6 7 8 9 : ; < = > ? nibble 4 | @ A B C D E F G H I J K L M N O 5 | P Q R S T U V W X Y Z [ \ ] ^ _ 6 | ` a b c d e f g h i j k l m n o 7 | p q r s t u v w x y z { | } ~ So the byte value that represents the 'W' character is $57 and the value that represents '}' is $7e. A Font ~~~~~~ As opposed to system programming, a font consists of the raw graphics data for each printable character supported. In most cases graphics are supplied for character in the range $20 to $7e, ie 'space' to '~'. Since each character is 1 byte wide and 8 lines high, 8 bytes are needed to define each characters graphics. Supplied with the manual is a PD font editor written by Kefrens. This allows fonts to be quickly designed and saved to disk as source code of the form: dc.w $00,$00,$00,$00,$00,$00,$00,$00 " " dc.w $18,$3c,$18,$18,$00,$18,$00,$00 "!" " " " " " " As you can see the data starts with the graphics for a space. Printing A Character ~~~~~~~~~~~~~~~~~~~~ In order to progress from an ASCII code, to a character visible on the display, the following steps are required: 1 Convert the ASCII code for the character into an index value. This can be achieved by simply subtracting the ASCII code for a space, $20, from the ASCII code of the character to print. 2 Use the index value to obtain the start address of this characters graphic data. This can be achieved by multiplying the index by 8, the number of bytes in each character, and adding this to the start address of the font. 3 Copy the graphics to the desired position in the bit plane. This involves copying successive bytes from the character data into successive lines of the bit plane. As well as the basic steps given above, a 'cursor' will also need to be maintained that specifies the next position in the bit plane at which to print a character. The conventional method involves tracking the X and Y coordinate at which a character is to be drawn. Since a character is a byte wide and a bit plane is a integer multiple of bytes wide, it makes sense to track the X position in bytes from the left edge of the bit plane. The Y position can be tracked in lines from the top of the bit plane. The address in a bit plane for any given X,Y coordinate can be calculated as follows: 1 Multiply the Y coordinate by the width of the bit plane to obtain the offset to the start of the line containing the first line of character data. 2 Add the X coordinate to this offset to obtain the offset to the first byte into which character data will be copied. 3 Add the bit plane start address to this offset to obtain the address of the first byte into which character data is to be copied. 4 The address of the bytes into which character data can be obtained by adding the width of the bit plane to this address after each line of character data is copied. Once a character has been copied, it makes sense to update the X,Y coordinates to the position at which the next character will be displayed. This can be achieved by simply incrementing the X coordinate. A subroutine will now be developed that will print successive characters from a string until a 0 byte is reached. The zero byte will be used to terminate all strings from this point on. This is a good convention to adopt and one that is widely used in the programming world. First, consider the fragment required to step through the string one character at a time. Assume that the routine is called with the address of the first character in register a0. ******************************** PrintString move.l a0,a4 move address into safe register prt_loop moveq.l #0,d0 clear work register move.b (a4)+,d0 d0=ASCII code of next character beq prt_done exit when code = 0 << rest of print routine >> bra prt_loop loop back for next character prt_done rts prt_X dc.w 0 X coordinate prt_Y dc.w 0 Y coordinate ******************************** The X,Y coordinates that will be used to determine the print position have been included in the above fragment for illustration only. The remainder of the print routine must preform the following steps: 1 Determine address of character graphics. 2 Determine address in bit plane to print start copying the graphics to. 3 Copy all 8 lines of graphics. 4 Update the X,Y coordinate. Each fragment will now be considered in turn. First, converting the ASCII code contained in d0 into the address of the character graphics data. Assuming that the font data is stored at the label 'FontData', the following fragment will suffice. sub.b #' ',d0 subtract ASCII code for space asl.w #3,d0 multiply by 8 add.l #FontData,d0 add start address of font move.l d0,a0 a0->character graphics Note that for speed, multiplying by 8 has been achieved by shifting the contents of d0 left three places. This small optimisation is often used. To determine the address in the bit plane, the X,Y coordinates stored at the labels 'prt_X' and 'prt_Y' will be used. Assuming the bit plane starts at the label 'bitplane' and that it is 320x256 pixels in size ( 40x256 byte ), the following fragment will suffice. moveq.l #0,d0 clear work register move.w prt_Y,d0 get Y coordinate mulu #40,d0 x width of bit plane add.w prt_X,d0 add X coordinate add.l #bitplane,d0 add bit plane start address move.l d0,a1 a1->destination in bit plane At this point a0 holds the address of the first byte of character data and a1 the address to copy it to. The following fragment will copy the character into the bit plane. Note that this is not an optimised fragment, but one that could be applied to bitplanes of widths other than 40 bytes. moveq.l #40,d0 width of bit plane move.b (a0)+,(a1) copy 1st byte of character add.l d0,a1 bump bit plane pointer move.b (a0)+,(a1) Copy 2nd byte of character add.l d0,a1 bump bit plane pointer move.b (a0)+,(a1) Copy 3rd byte of character add.l d0,a1 bump bit plane pointer move.b (a0)+,(a1) Copy 4th byte of character add.l d0,a1 bump bit plane pointer move.b (a0)+,(a1) Copy 5th byte of character add.l d0,a1 bump bit plane pointer move.b (a0)+,(a1) Copy 6th byte of character add.l d0,a1 bump bit plane pointer move.b (a0)+,(a1) Copy 7th byte of character add.l d0,a1 bump bit plane pointer move.b (a0)+,(a1) Copy 8th byte of character I have purposely opted not to use a loop in the above fragment. Loops require some initialisation and also slightly longer to execute. If the number of iterations of the loop is small and the loop body also small, it is often better to disregard the loop and write the routine out in full. ( NOTE. Other members of the 680x0 family of processors have an internal processor cache that allows them to execute very tight loops considerably faster than a basic 68000. Since the majority of Amigas are not fitted with one of these processors, this case is ignored here. ) All that is now required is to update the X,Y coordinate. This is achieved by first bumping the X coordinate. If the new X coordinate is still within the limit of the bitplane, in this example 0 to 39, no further action need be taken. If the X coordinate exceeds this limit, it should be reset to 0 and the Y coordinate bumped by 8, the height of one character. Similar checks should be preformed on the Y coordinate, but these will be ignored for the time being. A suitable fragment for updating the coordinates now follows. move.w prt_X,d0 get X addq.w #1,d0 bump X cmp.w #40,d0 boundary check blt.s prt_doneX skip if in range moveq.l #0,d0 reset X addq.w #8,prt_Y bump Y prt_doneX move.w d0,prt_X update X The complete string printing routine can now be formed by pulling all the above fragments together. The resulting subroutine is presented below. Note how comments have been added to the code. ******************************** ; Entry a0->start of string to print ; Corrupt a0,a1,a2,d0 PrintString move.l a0,a2 move address into safe register ; Get ASCII code of next character from the string prt_loop moveq.l #0,d0 clear work register move.b (a2)+,d0 d0=ASCII code of next character beq prt_done exit when code = 0 ; Calculate address of character data sub.b #' ',d0 subtract ASCII code for space asl.w #3,d0 multiply by 8 add.l #FontData,d0 add start address of font move.l d0,a0 a0->character graphics ; Calculate address in bit plane to start copying the character graphics moveq.l #0,d0 clear work register move.w prt_Y,d0 get Y coordinate mulu #40,d0 x width of bit plane add.w prt_X,d0 add X coordinate add.l #bitplane,d0 add bit plane start address move.l d0,a1 a1->destination in bit plane ; Copy character graphics into the bit plane. moveq.l #40,d0 width of bit plane move.b (a0)+,(a1) copy 1st byte of character add.l d0,a1 bump bit plane pointer move.b (a0)+,(a1) Copy 2nd byte of character add.l d0,a1 bump bit plane pointer move.b (a0)+,(a1) Copy 3rd byte of character add.l d0,a1 bump bit plane pointer move.b (a0)+,(a1) Copy 4th byte of character add.l d0,a1 bump bit plane pointer move.b (a0)+,(a1) Copy 5th byte of character add.l d0,a1 bump bit plane pointer move.b (a0)+,(a1) Copy 6th byte of character add.l d0,a1 bump bit plane pointer move.b (a0)+,(a1) Copy 7th byte of character add.l d0,a1 bump bit plane pointer move.b (a0)+,(a1) Copy 8th byte of character ; Update the X,Y print position move.w prt_X,d0 get X addq.w #1,d0 bump X cmp.w #40,d0 boundary check blt.s prt_doneX skip if in range moveq.l #0,d0 reset X addq.w #8,prt_Y bump Y prt_doneX move.w d0,prt_X update X bra prt_loop and loop for next character prt_done rts prt_X dc.w 0 X coordinate prt_Y dc.w 0 Y coordinate ******************************** To use this subroutine, a fragment similar to that shown below could be used. lea message,a0 a0->ASCII string to print bsr PrintString print it message dc.b 'This text will be printed!',0 even Example 1 shows how this subroutine can be used in a program. The example is basically the same as Example 2 from Chapter 5, The Display. It sets up a single bit plane, low resolution display and then waits for the user to press the left mouse button. When this happens, a message is displayed using the above routine and the program then waits for the right mouse button to be pressed before finishing. The example subroutine introduced above is a very basic printing routine. It lacks certain features required in a general purpose routine, such as colour control, font control, text positioning and more control over boundary checking. Further more the routine should be portable, requiring little or no modification when used in different programs that support different sized bit planes. The implementation of colour would require the graphics for each character to be copied into more than one bit plane. Also, graphics may already be displayed on the screen, so the print routine must be able to deal with this situation. Supplied with the manual is just such a subroutine. In principal, it works just as the above routine did. However, control codes can be embedded within an ASCII string that cause a specific task to be preformed. The printing colour, the font used, the printing mode and even the X,Y positions can all be changed from within a text string. This routine is located in the 'HW_Text.i' include file. Each control character consists of a single byte of data. To simplify the construction of strings to print, all the control bytes have been equated in the file 'HW_Macros.i'. The name, function and syntax of all the control bytes is given below. This information is duplicated in Appendix 1, The Include Files. Name Byte Syntax Function ~~~~ ~~~~ ~~~~~~ ~~~~~~~~ FEND $00 FEND Marks the end of a string. FEXIT $01 FEXIT Causes premature exit from print routine. FCOLOUR $02 FCOLOUR,n Changes the colour used for printing. The control byte should be followed by the number number to use. Eg, to use colour 3: dc.b FCOLOUR,3 FPOS $03 FPOS,X,Y Start printing from specified X,Y position. X should be specified as bytes from the left side of the bit plane, Y as lines from the top of the bit plane. Eg, To start printing 4 bytes across, 20 lines down: dc.b FPOS,4,20 FFONT $04 FFONT,n Start using a new font. The routine can recognise up to 8 fonts, numbered 1 to 8. Eg. Switch to font 3: dc.b FFONT,3 FCENTER $05 FCENTER Centralises the following ASCII string in the bit plane when printing. NOTE, this code must be followed by an ASCII string that terminates with either an FEND, FEXIT, FPOS or $0a byte. Eg. To centralise the text 'Mega Demo': dc.b FCENTER,'Mega Demo',$0a FMODE $06 FMODE,n Selects the mode of printing to use. Two modes are supported, SPLAT and BLEND. In SPLAT mode, the character graphics overwrite data already contained in the bit plane. In BLEND mode, the character graphics are merged with any data already contained in the bit plane. Eg. To select SPLAT mode ( this is the default mode ) dc.b FMODE,SPLAT FSINGLE $07 FSINGLE Switches on single character printing. The routine will always return after printing a single ASCII character. dc.b FSINGLE FSTRING $08 FSTRING Switches off single character printing. dc.b FSTRING $0a $0a Moves the cursor to the start of the next line. Note, this sets X=0 and Y=Y+8. Error checks are made on the new position. Eg. To print a line of text and then move down a line on the display and print another line of text underneath the first: dc.b 'This is line 1.',$0a dc.b 'This is line 2.' Single character mode is for use in interrupt routines, demonstrated shortly. The subroutine can store 8 font pointers. It comes with a default font and all 8 pointers default to this font. This may seem confusing at present, but a number of examples follow shorly that demonstrate the use of these codes. The subroutine is located at the label 'WriteText'. When called, a0 should contain the address of the string to print. Before using the routine, a few variables need to be initialised. These contain details of the bit planes used and make the routine portable. The variables are best set using a macro provided in 'HW_Macros.i', FONTSCREEN. This macro has the following syntax. FONTSCREEN bit plane address (,width) (,height) (,depth) Width, height and depth have default values 40, 256 and 4 respectively. If you are using bit planes with these dimensions, you need not specify them. The start address of the first bit plane must be specified however. Some examples of using the macro are given below. FONTSCREEN #bitplane FONTSCREEN a0,40,200,3 FONTSCREEN #bitplane,80,512,4 Note when passing a label for the start address of the bit planes, it must be preceded by a '#' character. The routine itself corrupts only one register, a0. This always contains a pointer to the next character to be printed. If the end of the string has been reached, then a0 will point to the 0 byte at the end of the string. The reason for adopting this method is to allow the routine to be repeatedly called from within an interrupt. Each time it is called, just a single character can be printed, the pointer returned by the routine should be passed back to the routine next time it is called. This is demonstrated in an example shortly. Printing Text ~~~~~~~~~~~~~ To use the routine 'WriteText', 'HW_Text.i' must be included AFTER 'HW_Start.i' at the beginning of a program. Example 2 uses a 320x256x4 display and simply prints a multi-coloured message. The bit planes start at the label 'screen', so the following initialisation is required: FONTSCREEN #screen Since the bit planes are the same dimension as the default settings, the other variables need not be specified. The string itself can be located at the label 'IntroText'. Note how the control codes have been embedded into the text. Each control code has been declared on a line of it's own for clarity. Adding Fonts ~~~~~~~~~~~~ As stated earlier, up to eight fonts can be accessed by 'WriteText'. Only one font is contained in the file 'HW_Text.i', others will have to be included as either source files, or raw data files. Once another font is introduced to a program, 'WriteText' needs to be informed. This is best achieved using another macro from 'HW_Macros.i' called SETFONT. This macro has the following syntax. SETFONT font number, data address As with FONTSCREEN, if you are passing a label as the data address it should be preceded by a '#' character. The font number can be any value from 1 to 8. Below are some examples of using this macro. SETFONT 2,#MyFont SETFONT 3,a0 Example 3 includes a second font as source. Note that the file is included BEFORE the label 'Main' and that it's full path is specified. A68K gets confused if you try to include files part way into a file, so it is safest to include all files at the beginning. Provided your program starts at the label 'Main', it will still be called correctly. This example demonstrates how to include the font data source into the program, how to pass the font to 'WriteText' and how to access the font while printing a text string. The screen used in this example is only 200 lines high and 3 bit planes deep, note the use of the FONTSCREEN macro. Interrupt Printing ~~~~~~~~~~~~~~~~~~ 'WriteText' can be switched into single character mode. This results in just one ASCII character being printed each call, the address of the next character being returned by the subroutine. This mode is intended for use within an interrupt routine. For example, a program executing a vertical blank interrupt handler and calling 'WriteText' from within this handler, in single character mode. The result is that the user can see the text being typed to the screen. Example 4 uses this technique. Several lines of text are typed, with more than one font being used. Try changing some of the control codes, especially the FCENTER one. This code is a great help in text presentation. Other Routines ~~~~~~~~~~~~~~ Also supplied in 'HW_Start.i' are two routine for converting a value contained in register d0 into a printable ASCII string. One routine will convert the word contained in d0, the other the long word. These routines are useful for printing the players score during a game, or for debugging your own programs. 'WordCon' is the subroutine that converts the word value in d0 into an ASCIIstring . It requires the address at which to generate the ASCII string to be passed in register a0. Note the routine always generates a string 5 characters long, since the largest value that can be contained in a word is 65535. Note also that both routines will pad the string out with leading '0' characters. So if d0 contained the value 65, the following string would be produced '00065'. 'LongCon' is the routine that converts the long word value in d0 into an ASCII string. Again pass the address at which to generate the string in register a0. Note that 'LongCon' always generates a string 10 characters long. Example 5 uses 'WordCon' to convert a counter into printable form and then uses 'WriteText' to display it. The counter is bumped during each vertical blank interrupt and the new value printed. Foot Note ~~~~~~~~~ This chapter has introduced a fairly simple method of printing to the screen. This method can be used in many types of program. The more impressive design of scroll texts is covered later, after the introduction of the Blitter. -- End Of File --