Secrets of text output

ZXNet echo conference «code.zx»

From Ivan Roshin To All 24 March 2001

Hello, All! ═══════════════════ text_out.1 ══════════════════ (c) Ivan Roshchin, Moscow Fido: 2:5020/689.53 ZXNet: 500:95/462.53 E-mail: asder_ffc@softhome.net WWW: http://www.zx.ru/echo/roschin Secrets of text output ───────────────────────── ("Radio Amateur. Your Computer" 2-3/2001) Here I will talk about optimization techniques used in displaying text messages on the ZX Spectrum screen. Some of these techniques can be used on other computers platforms where text is printed in graphic mode. First, a few general words. As you know, in the ZX Spectrum a single graphics mode with a resolution of 256*192 is implemented. The colors in it are set not for each point, but for the whole at once square 8*8 - that is, in fact, we have not real color image, and the colorized one is black and white. In other computers (for example, PC), along with graphic There are usually text modes as well. Spectrum is deprived of this capabilities, and it has to display text in graphical mode. On the one hand, it is slower and takes up more memory, but on the other hand - the size, shape and location of the symbols can be anything. In graphic mode it also becomes smooth scrolling is possible when viewing text, which is undoubtedly improves reading convenience.I will consider the most common case, when the font is specified in raster form (there are also vector fonts), and all its characters have the same width and height. When printing, it can be used as an 8*8 font located in ROM at addresses #3D00-#3FFF (only images are stored there characters with codes #20-#7F), and the font loaded into RAM (the size and number of characters in which, of course, can be arbitrary). The character printing procedure usually deals with such parameters: character code (byte), coordinates (x,y) and color. Coordinates are most often specified not in pixels, but in familiar places (By familiarity areas I mean areas the size of one character). Let's say, when using an 8*8 font, 32 will fit on the screen symbols horizontally and 24 vertically; accordingly, The x coordinate can vary from 0 to 31, and the y coordinate can vary from 0 to 23. The origin (0,0) is traditionally located in the upper left corner of the screen. As you can see, there is some emulation of text mode using graphics. In 99% of cases, one is used from the following three "text" modes: 32*24 (font 8*8), 42*24 (font 6*8) and 64*24 (font 4*8). (As you can see, in in 42*24 mode, four horizontal pixels remain unused - usually they are either evenly distributed right and left, or left on one side.)In 32*24 mode, each character can have its own color (which directly follows from the structure of the Spectrum screen). B in 42*24 and 64*24 modes this is impossible, but there every word can be painted in its own color (assuming that the words are separated spaces). Other modes (say 51*24 - when using font 5*8) are deprived of this too. I’ve said enough about theory, it seems. Now let's begin optimize! :) Usually in a font there is space for the image of each character. eight bytes. But quite often this idea turns out to be redundant because some bits are not used. For example, for font 6*8 (Fig. 1) the two outer columns are not used and are always equal to zero. byte 1 = 00000000 ░░░░░░░░░░░░░░░░ byte 2 = 00111000 ░░░░██████░░░░░░ byte 3 = 01101100 ░░████░░████░░░░ byte 4 = 01101100 ░░████░░████░░░░ byte 5 = 01111100 ░░██████████░░░░ byte 6 = 01101100 ░░████░░████░░░░ byte 7 = 01101100 ░░████░░████░░░░ byte 8 = 00000000 ░░░░░░░░░░░░░░░░ Fig. 1 When the font is located in RAM, this representation of it is quite justified as providing sufficient printing speed. (By the way, when they want to print 6*8 characters especially fast, use as many as four sets of characters, eachwhich are shifted relative to the other by two pixels along horizontal - so as not to waste time shifting when printing each character.) But to save disk space occupied your program, it makes sense to remove all redundant information. The savings in this case can be quite significant: for example, the font out of 256 characters 6*8, which initially occupied #800 bytes, will be reduced by a quarter. And if in such a font the image of characters is real occupies only 5*6 pixels (i.e. between characters there is mandatory space of one pixel horizontally and two along vertical), then it will be reduced by more than half! Below is a procedure to remove redundant material from a font. information. The meaning of the constants font_sx, font_x, used in it font_sy and font_y are explained in Fig. 2. ┌────────────────────────────┐ │ There should be a picture here │ │ from the file 'ris_2.scr' │ │ │ │ │ │ │ │ │ │ │ │ │ └────────────────────────────┘ Fig. 2 ════════════════════════ ════════════════════════ Best regards, Ivan Roshchin.

From Ivan Roshin To All 24 March 2001

Hello, All! ═══════════════════ text_out.3 ══════════════════ However, if you need to save RAM, you can leave the font in compressed form, sacrificing speed print. Well, so that the speed doesn’t suffer too much, you can use a few more optimization techniques. For example, if If a space is printed, it will be faster to just clear it corresponding familiar place on the screen. When printing a character it has it makes sense to first restore its image in a special buffer and from there display it on the screen, taking into account that if a character with the same code as the previous one is printed, then it the image has already been formed in the buffer (a kind of caching). Checking whether character codes match can be implemented something like this: ............... ; Printable character in register A, CP 0 ; compare it with the previous code LAST_S EQU $-1 ; character (stored in the command itself). JR Z,GO_PRN ; If they match, we display the contents ; buffers LD(LAST_S),A ; otherwise we remember the symbol code and ............... ; we form its image in the buffer. GO_PRN ............... ; Displays the contents of the buffer. Another way to reduce font size, which can used in conjunction with the previous one - delete from itunused characters. For this you can use with this procedure: SOURCE EQU #8000 ; source font address (256 characters) DESTINY EQU #C000 ; converted font address HIGH EQU 8 ; how many bytes does one character take? ORG #6000 LD IX,TAB_DEL LD HL,SOURCE LD DE,DESTINY XOR A ; current character NEXT_S PUSH AF CALL CHECK LD BC,HIGH JR NZ,NO_LDIR LDIR; resets BC NO_LDIR ADD HL,BC POP AF INC A JR NZ,NEXT_S RET ; The table of removed characters is presented ; as a bit array of size 256 ; bit (32 bytes). If an array element ; is equal to one, the corresponding symbol ; will be removed from the font. TAB_DEL DB %00000000,%00000000 DB %00000000,%00000000 DB %00000000,%00000000 DB %00000000,%00000000 DB %00000000,%00000000 DB %00000000,%00000000 DB %00000000,%00000000 DB %00000000,%00000000 DB %00000000,%00000000 DB %00000000,%00000000 DB %00000000,%00000000 DB %00000000,%00000000 DB %00000000,%00000000 DB %00000000,%00000000 DB %00000000,%00000000 DB %00000000,%00000000 ; The CHECK procedure is intended to ; working with bit arrays of length; up to 256 elements. ; ; Input: IX - array address; ; A - element number. ; Output: if acc. array element ; is 0, the Z flag is set. ; The value of A has been changed. ; ; If you replace the BIT command with SET or ; RES, you can not only check values ; elements of the array, but also change them. CHECK PUSH AF ; saved item number ; Working with an array element occurs ; using the BIT N,(IX+S) command, which ; before this is formed in memory. ; The values of N and S are calculated using the formula: ; S = high 5 bits of element number ; (byte number in the array where it is located ; desired element); ; N = 8 - low 3 bits of element number ; (bit number in the array byte; elements ; are located in a byte from left to right, ; and the bits are numbered backwards). ; ; The command takes up 4 bytes in memory and ; looks like this: ; ; #DD #CB S %01NNN110 ; │ │ │ └┤└┬┘└┬┘ ; │ │ │ │ │ └── common part for BIT, SET, RES ; │ │ │ │ └───── bit number ; │ │ │ └─────── %01 for BIT, %11 for SET, %10 for RES ; │ │ └─────────── displacement ; └───┴────────────── prefixes AND 7 RLCA RLCA RLCA XOR %01111110 ; %11111110 for SET, %10111110 for RES LD(CHECK_1+3),A POP AF RRCA RRCA RRCA AND %00011111 LD(CHECK_1+2),A CHECK_1 BIT 0,(IX) RET════════════════════════ ════════════════════════ Best regards, Ivan Roshchin.

From Ivan Roshin To All 24 March 2001

Hello, All! ═══════════════════ text_out.2 ══════════════════ SOURCE EQU #8000 ;the source font is located here, DESTINY EQU #C000 ;and here we will place the packaged... KOLVO EQU #100 ;number of characters (1-256) font_sx EQU 1 ;these parameters define the part used font_sy EQU 1 ;8*8 matrices for the converted font font_x EQU 5 font_y EQU 6 DEST_LEN EQU ((font_x*font_y*KOLVO)+7)/8 ;packed length ;font in bytes ORG #6000 LD HL,SOURCE LD IX,DESTINY LD E,0 LD B,KOLVO M1 PUSH BC PUSH HL LD BC,font_sy ADD HL,BC LD B,font_y M7 LD A,(HL) LD C,font_sx INC C M2 DEC C JR Z,M3 ADD A,A JR M2 M3 LD C,font_x INC C M4 DEC C JR Z,M5 ADD A,A RL (IX) INC E BIT 3,E JR Z,M4 INC IX LD E,0 JR M4 M5 INC HL DJNZ M7 POP HL LD BC,8 ADD HL,BC POP B.C. DJNZ M1 DEC E M6 INC E RET Z BIT 3,E RET NZSLA (IX) JR M6 After loading the program from disk, naturally, this font will need to be converted to its original form. Here appropriate procedure (if it is known in advance what the values of the constants, then it can be optimized): SOURCE EQU #8000 ;the packaged font is located here, DESTINY EQU #C000 ;and here we will unpack... KOLVO EQU #100 ;number of characters (1-256) font_sx EQU 1 ;see previous procedure font_sy EQU 1 font_x EQU 5 font_y EQU 6 ORG #6000 LD HL,DESTINY PUSH HL LD DE,DESTINY+1 LD BC,KOLVO*8-1 LD(HL),0 LDIR LD IX,SOURCE POP HL LD E,8 LD D,(IX) LD B,KOLVO M1 PUSH BC PUSH HL LD BC,font_sy ADD HL,BC LD B,font_y M7 LD C,font_x XOR A M3 SLA D ADC A,A DEC E JR NZ,M2 LD E,8 INC IX LD D,(IX) M2 DEC C JR NZ,M3 LD C,8-(font_sx+font_x) INC C M4 DEC C JR Z,M5 ADD A,A JR M4 M5 LD (HL),A INC HL DJNZ M7 POP HL LD BC,8ADD HL,BC POP B.C. DJNZ M1 RET ════════════════════════ ════════════════════════ Best regards, Ivan Roshchin.

From Ivan Roshin To All 24 March 2001

Hello, All! ═══════════════════ text_out.4 ══════════════════ It may also be that you yourself do not know which stamp symbols are produced in your program and which ones are not. In this In this case, insert the symbol below into the printing procedure. snippet and run your program. After finishing her work the TAB_DEL bit array will contain information about which characters can be removed from the font (in the same format as the previous example). The operating logic is as follows: every time the procedure is called When printing a character, the corresponding bit in the array is reset to zero, and As a result, only those elements of the array remain equal to one which correspond to characters that have never been printed in all program running time. ; Starting the printing procedure: in battery ; code of the character to be printed. PUSH AF PUSH IX LD IX,TAB_DEL CALL RES_BIT POP IX POP AF ............ ; continuation of the printing procedure RET ; Auxiliary procedure similar ; the CHECK procedure discussed above: RES_BIT PUSH AF AND 7 RLCA RLCA RLCA XOR %10111110 LD(CHECK_1+3),A POP AF RRCA RRCA RRCA AND %00011111 LD(CHECK_1+2),A CHECK_1 RES 0,(IX) RET TAB_DEL DB #FF,#FF,#FF,#FFDB #FF,#FF,#FF,#FF DB #FF,#FF,#FF,#FF DB #FF,#FF,#FF,#FF DB #FF,#FF,#FF,#FF DB #FF,#FF,#FF,#FF DB #FF,#FF,#FF,#FF DB #FF,#FF,#FF,#FF There are some problems when using this font with character printing, namely with the calculation of the offset from the beginning font in which the image of this symbol is located. Here you can either use a table of removed symbols, or recode all text messages displayed in the program. To further reduce the number of characters used, you can replace Russian letters in the output text with similar ones Latin style. Here is the relevant procedure: TEXT EQU #8000 ;text start address LENGTH EQU #1234 ;text length ORG #6000 LD HL,TEXT LD BC,LENGTH M1 LD DE,TABLE-1 M2 INC DE LD A,(DE) INC DE AND A JR Z,M3 CP(HL) JR NZ,M2 LD A,(DE) LD(HL),A M3 INC HL DEC B.C. LD A,B OR C JR NZ,M1 RET ;Pairs of characters - what to replace with what: TABLE DB "A","A" DB "B","B" DB "C","C" DB "E","E" DB "H","H" DB "K","K" DB "M","M" DB "O","O"DB "P","P" DB "T","T" DB "X","X" DB "a","a" DB "c","c" DB "e","e" DB "k","k" DB "n","n" DB "o","o" DB "p","p" DB "x","x" DB "y","y" DB 0 ;end of table ════════════════════════ ════════════════════════ Best regards, Ivan Roshchin.

From Ivan Roshin To All 24 March 2001

Hello, All! ═══════════════════ text_out.5 ══════════════════ You just need to make sure that the images of the letters in the one you are using the fonts were really similar. If, for example, Latin the letters in the font are made thicker than Russian ones, then the result will be like in Fig. 3. ┌────────────────────────────┐ │ There should be a picture here │ │ from the file 'ris_3.scr' │ │ │ │ │ │ │ │ │ │ │ │ │ └────────────────────────────┘ Fig. 3 If your program uses a 6*8 font, to save memory, you can form images of symbols with codes 32-127 directly during printing using the ROM font. Here's an example a small program that generates a font in this way and prints all the resulting characters on the screen. ORG #6000 LD HL,#3D00 ; ROM font address LD DE,#8000 ; there will be a 6*8 font here LD BC,#300 ; font length NEXT_B LD A,D ; All characters are divided into CP #82 ; two groups: JR Z,NO_IZM ; 1) #2F-#5FCP #80 ; 2) #20-#2E,#60-#7F JR NZ,IZM_1 ; Character Conversion LD A,E ; carried out differently CP 15*8 ; depending on JR C,NO_IZM ; which group they belong to. IZM_1 LD A,(HL) ; Character Conversion PUSH BC; first group PUSH AF AND %00001111 RLCA LD B,A POP AF AND %11110000 OR B POP B.C. JR BYTE_OK NO_IZM LD A,(HL) ; Character Conversion BYTE_OK RLCA ; second group AND %11111100 LD(DE),A INC HL INC DE DEC B.C. LD A,B OR C JR NZ,NEXT_B ; The 6*8 font is ready, now we print ; all received characters: LD HL,#8000-#100 LD (#5C36),HL CALL 3435 LD A,2 CALL 5633 LD A," " PRINT_S PUSH AF RST 16 POP AF INC A CP 128 JR NZ,PRINT_S RET ┌────────────────────────────┐ │ There should be a picture here │ │ from the file 'ris_4.scr' │ │ │ │ ││ │ │ │ │ │ │ │ └────────────────────────────┘ Fig. 4 By the way, it would be possible to apply this method in STS debugger monitor - the 6*8 font is used there. And due to the freed up space it would be possible to implement Any new features in this debugger... ════════════════════════ ════════════════════════ Best regards, Ivan Roshchin.

From Ivan Roshin To All 24 March 2001

Hello, All! ═══════════════════ text_out.6 ══════════════════ Now I’ll say a few words about the special storage method font in memory, at which the printing procedure is more fast and short. Typically, a font stores eight bytes first, forming image of the first character, then eight bytes of the second character and so on. When printing a character, you must first (knowing its code and address location in memory of the font) calculate the address at which the first byte of the character image is located, and then in turn read the image byte and write it to video memory. Let the symbol code be specified in the accumulator, the address in the video memory (calculated from print coordinates) specified in register pair DE and it is known that the font is located from the FONT address. Then the procedure the print will look something like this: LD H,0 ;Address calculation LD L,A ;character image ADD HL,HL ;(10 bytes/65 clock cycles) ADD HL,HL ADD HL,HL LD BC,FONT ADD HL,BC LD B,8 ;Output on screen (to increase speed M1 LD A,(HL);cycle can be opened) LD(DE),A INC HL ;If the font is located from the address, INC D ; multiple of 8 - you can just INC L DJNZ M1 RET “Yes, this has all been known for a long time,” someone will say. I won'targue. Just pay attention to how many resources is spent on calculating the address of the symbol image. And if the height the characters will not be eight pixels, but, say, seven? Then the program will become even more complicated, because you can’t get by with three shifts, as when multiplying by eight... Meanwhile, there is a much more effective way storing the font in memory: the font starts at an address that is a multiple of 256, in the first 256 bytes the upper ones are located sequentially strings of all characters in the next 256 bytes - second from top lines, and so on. In this case, regardless of the height characters, calculating the address of the character image when printing Requires virtually no resources. Here is an example of the printing procedure symbol: LD H,FONT/256 ;Address calculation LD L,A ;character image ;(3 bytes/11 clock cycles) LD B,8 ;Output on screen (to increase speed M1 LD A,(HL);cycle can be opened) LD(DE),A INC H INC D DJNZ M1 RET Isn't it much more effective? True, there is one disadvantage: if the font does not store images of all 256 characters, then after conversion it will take up more memory space. (In general, the size will be H*256 bytes, where H is the height character.) Here is a procedure that converts a font from a regular one representations into a more efficient one:SOURCE EQU #8000 ;source font location address DESTINY EQU #C000 ;converted font will go here HIGH EQU 8 ;character height LD HL,SOURCE LD DE,DESTINY M1 PUSH DE LD B,HIGH M2 LD A,(HL) LD(DE),A INC HL INC D DJNZ M2 POP DE INC E JR NZ,M1 RET And here is a procedure that performs the reverse transformation: SOURCE EQU #8000 ;from DESTINY EQU #C000 ;where HIGH EQU 8 ;character height LD HL,SOURCE LD DE,DESTINY M1 PUSH HL LD B,HIGH M2 LD A,(HL) LD(DE),A INC H INC DE DJNZ M2 POP HL INC L JR NZ,M1 RET It remains only to say that although this method of storage font is considered quite famous, until recently I didn’t assumed its existence. And he told me about him GoBLiN/BMZ - thank you! ════════════════════════ ════════════════════════ Best regards, Ivan Roshchin.