To extend the GraphPro library for fonts, we have to introduce a new data type: The Font. We also have to create some routines to manipulate the fonts and to draw them to the screen. The goal of the fonts in GraphPro is to add a flexible way to print to the screen, while being simple enough to use and, of course, is very fast. With these goals in mind, we will plunge into a new topic: Fonts.
Because of all of these options, the FontType structure (Shown below) has many fields. Some of the fields are not specified by the user or an editor, as they are strictly used to speed the routines up. Here is the FontType structure:
Although the details are hard to see at high resolution, when the resolution is decreased by scaling, the difference is obvious:
Note: This is not simply blurring the font. To anti-alias a font the correct way, you have to take a vector font and find the amount of a pixel that is actually filled by the letter. If only half of the pixel is filled, then the pixel is lit with one half of the intensity. This simulates higher resolution which makes prolonged exposure to the font easier on the eyes.
If you have an interrupt reference, look up Interrupt 10, Service 11. It allows you to get access to the character sets used by the BIOS. You can modify them so that your text mode font is different, and you can also get a pointer to the fonts with subfunction 30h. This is front door to the fonts that come with your computer.
This interrupt requires a code in BH that specifies which font to get. It then returns the font pointer in ES:BP. Because it trashed the BP register, we have to be careful about accessing variable on the stack. Other than that though, this is a pretty straight-forward procedure.
The BIOS requires the following codes, passed in BH, to access the font sets:
The way I see it, there are many ways to do things. For example, when coding a comparison and a loop together, there are two equally correct ways to code it: (This is mildly pseudo code...)
With all of these goals in mind, I created the following procedure. It fulfulls all of what we have been looking for (It is just for 1 bit fonts though...), but is not as fast as we would like. In future articles, we will optimize this and add support for other color depths... Anyhoo:
Text: In it's many shapes and forms, it has provided us (humans) with the capability to communicate with millions of other people without having to spend years writing it out by hand. Since the invention of the printing press, everything from junk mail and political propaganda to literary masterpieces have been printed. With text, the ability to communicate is greatly enhanced and the availability of different fonts (typefaces) have made communicating emotions possible.
From now on, a "Font" references a structure in memory. This structure holds all of the pertinent details about the font and a pointer to the actual font data. Because we want to conserve memory but be as flexible as possible, there are a number of options that apply to fonts.
FontType = RECORD
Data : ScreenBufferPtr; { Pointer to an array of bytes }
Height, { Size of a single character }
Width,
Depth, { Depth in bits }
FirstChar, { First char in font }
LastChar, { Last char in font }
Background, { 0 = clear }
CharSize : BYTE; { Size of each character in bytes }
Size : WORD; { Size of the font in memory }
END;
Here is a short description of how each of the fields are used and why they are important:



CharSize := (Width*Height*Depth+7) DIV 8; { 8 bits per byte }
Size := CharSize*(LastChar-FirstChar+1)
Where are we going to get all of the beautiful fonts for use with these routines? There is two answers to that question... The first is that you can draw them all out. The second is that we can grab the fonts that are already on your system: Hiding in the BIOS. If you have a VGA card (You do if you are using these routines...), then you have three fonts that are built into the ROM on your computer. Their purpose is to define the characters shapes in text mode. They are also used to draw the text in graphics mode when you use the WRITE or WRITELN procedures, which in turn call on the BIOS. All three fonts are single bit fonts, and all are 8 pixels wide. Here are the three fonts and their use:
All we have to do is get a pointer to these fonts in memory and copy them into a Font structure. That way, we don't have to draw out all of our fonts for a quick demonstration... Although they don't look as good as 2, 4, or 8 bit fonts, they are functional.
Don't even bother to ask me how they came up with these numbers... it is beyond me. With these numbers, we can create a procedure that takes a height and returns the BIOS font in a Font structure. With this Font, we can draw text to the screen!
PROCEDURE LoadBIOSFont(VAR Font : FontType; Height : BYTE);
VAR Segm, Offs : WORD;
BEGIN
IF Font.Data <> NIL THEN
FreeFontMem(Font); { Release previously used font }
Font.Height := Height; { Fill out all of the default settings }
Font.Width := 8;
Font.Depth := 1;
Font.CharSize := (Font.Width*Font.Depth*Font.Height+7) SHR 3;
Font.FirstChar := 0;
Font.LastChar := 255;
CASE Height OF { Select the appropriate code. }
8 : Height := 3;
14 : Height := 2;
16 : Height := 6;
ELSE EXIT;
END;
GetFontMem(Font); { Allocate memory for the font }
ASM
push BP { Save Base Pointer }
mov BH, Height
mov AX, 1130h { Function 11h, Subfuntion 30h }
int 10h { Interrupt 10h }
mov AX, BP
pop BP
mov Offs, AX { Store the pointer }
mov Segm, ES
END;
Move(PTR(Segm, Offs)^, Font.Data^, Font.Size); { Copy the data! }
END;
Now we have the structures, the font in memory, and the groundwork necessary to display a font. All we need now is the actual routines to display the font on the screen... Which IS the topic of this article.
PROCEDURE ClearOrFillArray(Array : ArrayType; Clear : BOOLEAN);
VAR I : SuperLongInteger;
BEGIN
FOR I := 0 TO 10000000 DO
IF Clear THEN
Array[I] := 0
ELSE
Array[I] := 1;
END;
This hypothetical procedure merely goes through an array one element at a time and either sets or clears the element. This could also be coded like this:
PROCEDURE ClearOrFillArray(Array : ArrayType; Clear : BOOLEAN);
VAR I : SuperLongInteger;
BEGIN
IF Clear THEN
FOR I := 0 TO 10000000 DO
Array[I] := 0
ELSE
FOR I := 0 TO 10000000 DO
Array[I] := 1;
END;
They are both correct, but one is a heck of a lot faster. (No, I do not want to use FillChar! :) This little tangent serves to illustrate why we are going to break our font routines into separate routines for each font depth. For each pixel, we could do something like this in our inner loop:
Data := Data SHL Font.Depth;
BitsLeft := BitsLeft - Font.Depth;
Color := Data MOD (1 SHL Font.Depth);
Or we could do something like this way outside of our loops:
CASE Font.Depth OF
1 : WriteG1(Font, X, Y, Text, Color);
2 : WriteG2(Font, X, Y, Text, Color);
4 : WriteG4(Font, X, Y, Text, Color);
8 : WriteG8(Font, X, Y, Text);
Gee, which would be faster? :) Because of this, I have chosen to write seperate routines for each of the color depths, which is actually pretty easy. Lets start with one bit color:
To draw a font to the screen, we have to have three nested loops operating in each other. Here is the basic idea of what we are trying to do: (Again, pseudo code, this time it's slightly weirder...)
#1. FOR EachCharacterInString
#2. FOR EachLineInCharacter
#3. FOR EachPixelInLine
#4. IF Pixel THEN SetPixel(Pixel);
Of course, there is a lot of details that are needed to fill in between the steps. Here is what has to happen:
PROCEDURE WriteG1(Font : FontType; X, Y : INTEGER; Text : STRING; Color : BYTE);
VAR
C : BYTE; { ASCII code of current character }
CurChar : INTEGER; { Index into Text }
FontPtr : WORD; { Index into the font data... }
Xc, Yc : INTEGER; { X & Y Counts }
FontData, { The bit data }
BitsLeft : BYTE; { The number of bits left in FontData }
BEGIN
FOR CurChar := 1 TO LENGTH(Text) DO { Step over each char in string }
BEGIN
C := ORD(Text[CurChar]); { Grab the Ascii code of char }
IF (C <= Font.LastChar) AND (C >= Font.FirstChar) THEN { Range check }
BEGIN
FontPtr := (C-Font.FirstChar)*Font.CharSize; { Generate ptr to data }
BitsLeft := 0;
FOR Yc := Y TO Y+Font.Height-1 DO { Step over each pixel }
BEGIN { in the Y direction }
FOR Xc := X TO X+Font.Width-1 DO { Step over each pixel }
BEGIN { In the X direction }
IF BitsLeft = 0 THEN
BEGIN
FontData := Font.Data^[FontPtr]; { Refill FontData when }
INC(FontPtr); { it gets empty }
BitsLeft := 8;
END;
IF (FontData AND 128) = 128 THEN { Draw a pixel? }
SetPixel(Xc, Yc, Color)
ELSE IF (Font.Background > 0) THEN { Draw the background? }
SetPixel(Xc, Yc, Font.Background);
FontData := FontData SHL 1; { Next bit please! }
DEC(BitsLeft);
END;
END;
END;
INC(X, Font.Width); { Step to the right for }
END; { the next character }
END;
Now that we have a working text drawing procedure, we can go on to make our stunning demo of the power of the Font!! {Insert sound effect here}
This stuningly clever (well, maybe not...) example shows how to use the Background property and how to select fonts. The other main purpose of the example is to coalate all of the source code up 'til now into one place... This also shows how to animate with text...
PROGRAM Fonts;
USES CRT, { Need the Keypressed function }
GraphPro;
TYPE
FontType = RECORD
Data : ScreenBufferPtr; { Pointer to an array of bytes }
Height, { Size of a single character }
Width,
Depth, { Depth in bits }
FirstChar, { First char in font }
LastChar, { Last char in font }
CharSize, { Size of each character in bytes }
Background : BYTE; { 0 = clear }
Size : WORD; { Size of the font in memory }
END;
PROCEDURE GetFontMem(VAR Font : FontType);
BEGIN
Font.Size := (Font.Width*Font.Depth*Font.Height*
(Font.LastChar-Font.FirstChar+1) + 7) DIV 8;
GetMem(Font.Data, Font.Size);
END;
PROCEDURE FreeFontMem(VAR Font : FontType);
BEGIN
FreeMem(Font.Data, Font.Size);
Font.Data := NIL;
END;
PROCEDURE LoadBIOSFont(VAR Font : FontType; Height : BYTE);
VAR Segm, Offs : WORD;
BEGIN
IF Font.Data <> NIL THEN
FreeFontMem(Font); { Release previously used font }
Font.Height := Height; { Fill out all of the default settings }
Font.Width := 8;
Font.Depth := 1;
Font.CharSize := (Font.Width*Font.Depth*Font.Height+7) SHR 3;
Font.FirstChar := 0;
Font.LastChar := 255;
CASE Height OF { Select the appropriate code. }
8 : Height := 3;
14 : Height := 2;
16 : Height := 6;
ELSE EXIT;
END;
GetFontMem(Font); { Allocate memory for the font }
ASM
push BP { Save Base Pointer }
mov BH, Height
mov AX, 1130h { Function 11h, Subfuntion 30h }
int 10h { Interrupt 10h }
mov AX, BP
pop BP
mov Offs, AX { Store the pointer }
mov Segm, ES
END;
Move(PTR(Segm, Offs)^, Font.Data^, Font.Size); { Copy the data! }
END;
PROCEDURE WriteG1(Font : FontType; X, Y : INTEGER; Text : STRING; Color : BYTE);
VAR
C : BYTE; { ASCII code of current character }
CurChar : INTEGER; { Index into Text }
FontPtr : WORD; { Index into the font data... }
Xc, Yc : INTEGER; { X & Y Counts }
FontData, { The bit data }
BitsLeft : BYTE; { The number of bits left in FontData }
BEGIN
FOR CurChar := 1 TO LENGTH(Text) DO { Step over each char in string }
BEGIN
C := ORD(Text[CurChar]); { Grab the Ascii code of char }
IF (C <= Font.LastChar) AND (C >= Font.FirstChar) THEN { Range check }
BEGIN
FontPtr := (C-Font.FirstChar)*Font.CharSize; { Generate ptr to data }
BitsLeft := 0;
FOR Yc := Y TO Y+Font.Height-1 DO { Step over each pixel }
BEGIN { in the Y direction }
FOR Xc := X TO X+Font.Width-1 DO { Step over each pixel }
BEGIN { In the X direction }
IF BitsLeft = 0 THEN
BEGIN
FontData := Font.Data^[FontPtr]; { Refill FontData when }
INC(FontPtr); { it gets empty }
BitsLeft := 8;
END;
IF (FontData AND 128) = 128 THEN { Draw a pixel? }
SetPixel(Xc, Yc, Color)
ELSE IF (Font.Background > 0) THEN { Draw the background? }
SetPixel(Xc, Yc, Font.Background);
FontData := FontData SHL 1; { Next bit please! }
DEC(BitsLeft);
END;
END;
END;
INC(X, Font.Width); { Step to the right for }
END; { the next character }
END;
VAR
Font : FontType;
X, Y, DX, DY : INTEGER;
Text : STRING;
BEGIN
InitGraph;
ClrScr(1);
LoadBIOSFont(Font, 8);
WRITEG1(Font, 0, 0, 'This is size 8', 14);
LoadBIOSFont(Font, 14);
WRITEG1(Font, 0, 8, 'This is the BIOS''s 14 point font', 14);
LoadBIOSFont(Font, 16);
Font.Background := 14;
WRITEG1(Font, 0, 22, 'This is a 16 point font inverted', 1);
Font.Background := 0;
WriteG1(Font, 0, 50, 'Press