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.