VGAlib3

What follows is the IMPLEMENTATION MODULE for the VGAlib3 library for FST Modula-2. Have fun studying the source. It took me quite some time to get this thing going.... :o)

IMPLEMENTATION MODULE VgaLib3

IMPLEMENTATION MODULE VgaLib3;

(*  This is FREE software, as described in the GNU General Public Licences.
    Therefore it comes WITH THE FULL SOURCES. Please feel free to improve this
    code when necessary. You OWN this code.

    I would appreciate that people let in this message when extending this
    library, as a small tribute to me (for laying the foundation).

    In case people need extra information, contact me via:

        snail mail: Jan Verhoeven, 5012 GH 272, The Netherlands
   electronic mail: mocka@yahoogroups.com

    I remain full copyrights to these sources. If you want to send me a small
    "thanks", please send me a postcard of your hometown to the above shown
    snailmail address. Yes it is in code; the internal code of our national
    mail deliverer.

    Use this software at your own risk. Please find yourself a GNU GPL if you
    are in any doubt. I use these functions for all my own software, but there
    is NO GUARANTEE OF ANY KIND covering it.            *)
   

Some IMPORTing and vars are needed

First we do some IMPORTING. Some of these should not be necessary but for speed-reasons I did them anyway. An example is SHR (shift right) which might as well been implemented as a BITSET operation, but this is done in pure inline ASM.

FROM    barith      IMPORT  shr;
FROM    SYSTEM      IMPORT  ASSEMBLER, ADDRESS;
FROM    FileSystem  IMPORT  File, Lookup, Close, ReadNBytes, Response;
FROM    InOut       IMPORT  WriteString;
FROM    Strings     IMPORT  Length;
FROM    Storage     IMPORT  Available, ALLOCATE, DEALLOCATE;
FROM    System      IMPORT  Terminate;

VAR     fp      : File;
        Bread   : CARDINAL;
        BitMap  : ADDRESS;
   
So far the variables and code re-using. Time for some new code:

The code starts. First define some low level functions

PROCEDURE   SetVGA (NewMode : CARDINAL) : CARDINAL;     (*  Set screen to VGA mode NewMode  *)
                                                        (*  Return current mode             *)
VAR     OldMode     : CARDINAL;

BEGIN
    NewMode := NewMode MOD 256;
    ASM
        PUSH BP
        MOV  AH, 0FH
        INT  010H
        MOV  OldMode, AL
        MOV  AX, NewMode
        INT  010H
        POP  BP
    END;
    RETURN OldMode
END SetVGA;
   
When doing things with INT 0x10, always save the basepointer register (BP) since it is not sure it remains the same. And the FST compiler uses it a lot, so don't take the risk.

Mixing ASM and Modula-2

PROCEDURE   SetColour (Foreground, Background   : COLOUR);
                                            (*  Define colour to work with.     *)
VAR     Col     : CARDINAL;

BEGIN
    Col := ORD (Foreground) + 16 * ORD (Background);
    ASM
        MOV  DX, 03C4H          (* VGA controller port *)
        MOV  AH, Col
        MOV  AL, 2
        OUT  DX, AX
    END;
END SetColour;
   
This is one of the things what I like in FST Modula-2: perfect mix of high level coding and assembly support. No fancy variable fetching in ASM. Just do some in Modula and the rest Inline. The one line of Modula-2 here is used to fill a variable with a number, instead of two enumerated values.
PROCEDURE   SetMask (Mask : CHAR);          (*  Set up mask for plotting in VGA memory.  *)

BEGIN
    ASM
        MOV  DX, 03CEH          (* VGA controller port *)
        MOV  AH, Mask
        MOV  AL, 8
        OUT  DX, AX
    END;
END SetMask;
   

The graphical functions start here: Plot a dot

This is where the actual graphics action routines start. Before this were only support and kernel routines. Now we start building functionality.
We start off with the PLOT function. It plots one point at position (CurX, CurY) in the right colour.

PROCEDURE   Plot (VAR InWin : WinData);     (*  Plot point on CurX, CurY.   *)

VAR x, y    : CARDINAL;

BEGIN
    x := InWin.CurX + InWin.TopX;
    y := InWin.CurY + InWin.TopY;

    ASM
        MOV  AX, 0A000H
        MOV  ES, AX         (* Set up segment register *)
        MOV  CX, x
        AND  CX, 7          (* Which bit to plot? *)
        MOV  AH, 80H
        SHR  AH, CL         (* Compose plotting mask *)
        MOV  AL, 8
        MOV  DX, 03CEH
        OUT  DX, AX         (* Set plottingmask *)
        MOV  AX, y          (* Calculate offset in Video RAM *)
        MOV  BX, AX
        ADD  AX, AX
        ADD  AX, AX
        ADD  AX, BX         (* AX := 5 * Y *)
        MOV  CL, 4
        SHL  AX, CL         (* AX := 16 * 5 * Y *)
        MOV  BX, x
        SHR  BX, 1
        SHR  BX, 1
        SHR  BX, 1
        ADD  BX, AX         (* plus X / 8 *)
        MOV  AL, ES:[BX]
        MOV  AL, 0FFH
        MOV  ES:[BX], AL    (* and plot it *)
    END;
END Plot;
   
The plotting routine is based upon the following: This means that each and every pixel's byte address must be calculated and "mirrored" for finding the bitposition. I call this "composing the plotting mask". The physical byte address is calculated by multiplying the "y" coordinate by 80 and adding the "x" coordinate.

I refer to the definition of the Window Data, which is given in the DEFINITION MODULE:

      WinData = RECORD
                   TopX, TopY, Width, Height,
		   CurX, CurY, DeltaX, DeltaY,
                   Indent                          : CARDINAL;
		   BoxCol, TexCol, BckCol, MnuCol  : COLOUR;
		END;
   
This RECORD describes the following aspects of the screen we are working in:
TopX This is the "x" (or horizontal) value of the top right hand corner of the active window
TopY The "y" (or vertical) value of the top right hand corner of the active window
Width The width of this window, in pixels
Height The height of the window in pixels
CurX The value of the "x" coordinate within this window
CurY Value of "y" within this window
DeltaX Used by line drawing routines to find out how many pixels to draw horizontally
DeltaY Used by line drawing routines to find out how many pixels to draw or move up
Indent This is the minimum amount of whitespace there must be between text and either border
BoxCol Colour of the lines around the current window
TexCol Colour of the text that will be printed now
BckCol Colour of the background in this window
MnuCol Colour of the menu-bar (not used yet)

Each window has it's own WinData structure. Some windows even have two or more WinData records which can be handy if you need to do different things with one window. If a window needs no border, just have the BoxCol identical to the BckCol.

In an application program (about the Liedetector, called LDA04) I have set up a LED display on screen. In it, each digit has it's own WinData record, so that the decimals had another colour from the integers.

Each WinData record is an entity. It keeps track of the current "x" and "y" coordinates and all the colours. This makes programming a lot easier and making a user interface is also a piece of cake now.

The following routine comes very close to the Plot routine: UnPlot. A keen programmer could have merged the two, but I'm not keen.

PROCEDURE UnPlot (VAR InWin : WinData);         (*  Erase pixel on CurX, CurY.  *)

VAR x, y    : CARDINAL;

BEGIN
    x := InWin.CurX + InWin.TopX;
    y := InWin.CurY + InWin.TopY;

    ASM
        MOV  AX, 0A000H
        MOV  ES, AX         (* Set up segment register *)
        MOV  CX, x
        AND  CX, 7          (* Which bit to plot? *)
        MOV  AH, 80H
        SHR  AH, CL         (* Compose plotting mask *)
        MOV  AL, 8
        MOV  DX, 03CEH
        OUT  DX, AX         (* Set plottingmask *)
        MOV  AX, y          (* Calculate offset in Video RAM *)
        MOV  BX, AX
        ADD  AX, AX
        ADD  AX, AX
        ADD  AX, BX         (* AX := 5 * Y *)
        MOV  CL, 4
        SHL  AX, CL         (* AX := 16 * 5 * Y *)
        MOV  BX, x
        SHR  BX, 1
        SHR  BX, 1
        SHR  BX, 1
        ADD  BX, AX         (* plus X / 8 *)
        MOV  AL, ES:[BX]
        MOV  AL, 0
        MOV  ES:[BX], AL    (* and erase it *)
    END;
END UnPlot;
   

Line drawing

Now that we can plot pixels, it should be no big deal to draw lines. That is true, but for long lines I want to plot 8 pixels at once, freeing up lots and lots of time to do sensible things, like waiting for the user to move the mouse or press a key. :o)

In order to find the optimum linedrawer I set up a condition:

Why 18 pixels? 1 bit in Eval, 16 bits in Kval and 1 bit in Lval. That makes 18.

Anyway: for the long line plotting there are some special vales needed:


OK, the formulas have a high assembly language content syntax, but they should be understandable by most of the programmers.

PROCEDURE DrawH (VAR InWin : WinData; Flag : BOOLEAN);

(*  Draw a horizontal line from CurX, CurY for DeltaX pixels.  *)

VAR Index, Stop         : CARDINAL;
    x, dx, y, Kval      : CARDINAL;
    Emask, Lmask, Val   : CHAR;

BEGIN
    IF Flag THEN        (* Flag = TRUE => Plot, else UnPlot *)
        Val := 0FFX;
    ELSE
        Val := 0X;
    END;
    IF InWin.DeltaX < 18 THEN
        FOR Index := 0 TO InWin.DeltaX DO       (* For short lines *)
            Plot (InWin);
            INC (InWin.CurX);
        END;
    ELSE
         x := InWin.TopX + InWin.CurX;          (* For long lines *)
         y := InWin.TopY + InWin.CurY;
        dx := InWin.DeltaX;
        ASM
            MOV  AX, 0A000H
            MOV  ES, AX         (* Set up segment register *)
            MOV  CX, x
            AND  CX, 7
            MOV  BX, 8
            SUB  BX, CX
            MOV  AL, 0FFH
            SHR  AL, CL
            MOV  Emask, AL      (* compose plotting mask *)
            MOV  CX, dx
            SUB  CX, BX
            MOV  AX, CX
            AND  AX, 7
            PUSH AX             (* Save L-val *)
            SUB  CX, AX
            SHR  CX, 1
            SHR  CX, 1
            SHR  CX, 1
            MOV  Kval, CX
            MOV  AL, 0
            POP  CX             (* retrieve L-val *)
            JCXZ L0
            MOV  AL, 080H
        L0: DEC  CX
            SAR  AL, CL
            MOV  Lmask, AL

            MOV  AX, y              (* Calculate offset in Video RAM *)
            MOV  BX, AX
            ADD  AX, AX
            ADD  AX, AX
            ADD  AX, BX             (* AX := 5 * Y *)
            MOV  CL, 4
            SHL  AX, CL             (* AX := 16 * 5 * Y *)
            MOV  BX, x
            SHR  BX, 1
            SHR  BX, 1
            SHR  BX, 1
            ADD  BX, AX             (* plus X / 8 *)

            MOV  AH, Emask
            MOV  DX, 03CEH
            MOV  AL, 8
            OUT  DX, AX             (* Set plotting mask *)

            MOV  AL, Val
            MOV  AH, ES:[BX]
            MOV  ES:[BX], AL        (* Do the plotting ... *)

            INC  BX
            MOV  CX, Kval
            JCXZ L2
            MOV  AX, 0FF08H
            OUT  DX, AX
            MOV  AH, Val
        L1: MOV  AL, ES:[BX]
            MOV  ES:[BX], AH
            INC  BX
            LOOP L1
        L2: MOV  AH, Lmask
            MOV  AL, 8
            OUT  DX, AX
            MOV  AL, ES:[BX]
            MOV  AL, Val
            MOV  ES:[BX], AL
        END;
        INC (InWin.CurX, dx);
    END;
END DrawH;
   
So far the horizontal lines. For vertical lines it's easy: optimizations are not possible. So we just need to plot each pixel one by one. The accompanying routine is very simple and uses the brute force of assembly language:
PROCEDURE DrawV (VAR InWin : WinData);
  
(*  Draw a vertical line from CurX, CurY for DeltaY pixels. 100% optimized for speed.  *)

VAR x, y, dy    : CARDINAL;

BEGIN
     x := InWin.CurX + InWin.TopX;
     y := InWin.CurY + InWin.TopY;
    dy := InWin.DeltaY;
    ASM
        MOV  AX, 0A000H
        MOV  ES, AX         (* Set up segment register *)
        MOV  CX, x
        AND  CX, 7          (* Which bit to plot? *)
        MOV  AH, 80H
        SHR  AH, CL         (* Compose plotting mask *)
        MOV  AL, 8
        MOV  DX, 03CEH
        OUT  DX, AX         (* Set plottingmask *)
        MOV  AX, y          (* Calculate offset in Video RAM *)
        MOV  BX, AX
        ADD  AX, AX
        ADD  AX, AX
        ADD  AX, BX         (* AX := 5 * Y *)
        MOV  CL, 4
        SHL  AX, CL         (* AX := 16 * 5 * Y *)
        MOV  BX, x
        SHR  BX, 1
        SHR  BX, 1
        SHR  BX, 1
        ADD  BX, AX         (* plus X / 8 *)
        MOV  CX, dy
    L0: MOV  AL, ES:[BX]
        MOV  AL, 0FFH
        MOV  ES:[BX], AL    (* and plot it *)
        ADD  BX, 80
        LOOP L0
    END;
    INC (InWin.CurY, dy);
END DrawV;
   

Writing text

Now that we can draw lines and hence make boxes, it's time to put some readable data into these boxes. For this we need to be able to:

I have defined a set of tokens of size 8 x 16. I think this is a reasonable size. If you disagree, just change the bitmap file (I composed mine with the A86 assembler) to form any size you want.

The routine also handles the following conditions:

Condition Action
Linefeed The cursor is placed one line of text lower. This is NOT one pixel, but the number of lines of one pixel. In this case: 16.
Carriage
Return
The cursor is placed at the start of the line for text. This means that the cursor is put at the position defined by 'Indent'.
Indent The software checks if the upcoming token does not pass the pixel (TopX + Width - Indent) meaning that the right window border is never overwritten by text or tokens.

For the rest it's all quite straightforward. Analogous to line drawing we define two variables:

Cval This is the number of bits that we are "off center". In fact, it is the number of bits that have to be plot in the second byte: Cval = xpos AND 7
Pmask The plotting mask for characters. This is a two byte value. The high part is used for plotting the first pixels and the low part for the remaining pixels.
Pmask = 0xFF00 shr Cval

What follows is the repeated plotting of a series of pixels for the first byte and next the same for the second byte, if needed. PlotChar is a powerful function since it allows us to drop tokens at ANY position on screen.

PROCEDURE PlotChar (VAR InWin : WinData; Letter : CHAR);

(*  Plot character on InWin.(CurX,CurY).    *)

VAR xpos,   ypos,   MapOfs,
    VGApos, VGAseg, Pmask   : CARDINAL;
    Cval                    : CHAR;

BEGIN
    IF Letter = 0AX THEN
        INC (InWin.CurY, 16);           (* Process LF *)
        RETURN;
    END;
    IF Letter = 0DX THEN
        InWin.CurX := InWin.Indent;     (* Process CR *)
        RETURN;
    END;
    IF InWin.CurX >= InWin.Width - ChrWid THEN
        InWin.CurX := InWin.Indent;
        INC (InWin.CurY, 16);
    END;
    xpos := InWin.CurX + InWin.TopX;
    ypos := InWin.CurY + InWin.TopY;
    VGApos := 80 * ypos + shr (xpos, 3);
    VGAseg := 0A000H;
    MapOfs := ORD (Letter) * 16;
    ASM
        PUSH ES             (* save ES *)
        MOV  CX, xpos
        AND  CX, 7
        MOV  Cval, CL       (* nr of bits "off center" *)
        MOV  BX, 0FF00H
        SHR  BX, CL
        MOV  Pmask, BX      (* mask to use for left and right halves *)
        MOV  AX, BX
        MOV  AL, 8
        MOV  DX, 03CEH
        OUT  DX, AX         (* set plotting mask for left part *)
        MOV  CX, 16
        MOV  BX, VGApos
        LES  SI, BitMap     (* here are the pixels that make the tokens *)
        ADD  SI, MapOfs
    L0: PUSH CX
        LES  AX, BitMap     (* load ES, AX is just scrap *)
        MOV  AH, ES:[SI]    (* load pattern *)
        MOV  CL, Cval
        SHR  AX, CL         (* compose left half *)
        MOV  ES, VGAseg
        MOV  AL, ES:[BX]
        MOV  ES:[BX], AH    (* and "print" it *)
        ADD  BX, 80         (* point to next row *)
        INC  SI             (* and next pixel pattern *)
        POP  CX
        LOOP L0             (* repeat until done *)
        MOV  AX, Pmask
        CMP  AL, 0          (* if Cval = 0 => perfect allignment *)
        JE   ex             (*   skip second half *)
        XCHG AH, AL         (* else repeat the story once more *)
        MOV  AL, 8
        OUT  DX, AX         (* set up mask for right half *)
        MOV  CX, 16
        SUB  BX, 1279       (* 16 x 80 - 1 *)
        SUB  SI, CX
    L1: PUSH CX
        LES  AX, BitMap
        MOV  AH, ES:[SI]
        MOV  AL, 0
        MOV  CL, Cval
        SHR  AX, CL
        MOV  ES, VGAseg
        MOV  AH, ES:[BX]
        MOV  ES:[BX], AL
        ADD  BX, 80
        INC  SI
        POP  CX
        LOOP L1
    ex: POP  ES
    END;
    INC (InWin.CurX, ChrWid);   (* point to next printing position *)
END PlotChar;
   
Now that we can plot one character, we must find a way to do strings of tokens:
PROCEDURE PlotText (VAR InWin : WinData; String : ARRAY OF CHAR);

(*  Print a string of text to the screen.  *)

VAR n   : CARDINAL;

BEGIN
    n := 0;
    SetColour (InWin.TexCol, InWin.BckCol);
    WHILE (n <= HIGH (String)) & (String [n] # 0X) DO
        PlotChar (InWin, String [n]);
        INC (n);
    END;
END PlotText;
   
See? After all the hard work in assembly, we switch back to the advantages of Modula-2. What follows is pure Modula-2. We have optimized what is slow. The rest must come from the compiler.

Auxillary functions.

Center is a routine to drop a string of tokens on screen, where the rouitne nicely interspaces the individual words or phrases.
The routine knows the window width. It then starts counting the number of tokens and spaces. It will reserve CharWid pixels for each token. Now we can calculate:

         Between = (Pixels - Tokens) / Spaces
   
to find out how many spaces we can take between words in order to fill up the line evenly.
PROCEDURE Center (InWin : WinData; String : ARRAY OF CHAR);

(*  Center a line of text in InWin box.     *)

VAR Pixels, Chars, Spaces,
    Index, Between          : CARDINAL;
    Letter                  : CHAR;

BEGIN
    Pixels := InWin.Width - 2 * InWin.Indent - InWin.CurX;
    Spaces := 0;
    Chars  := 0;
    Index  := 0;
    WHILE (Index <= HIGH (String)) & (String [Index] # 0X) DO
        Letter := String [Index];
        IF Letter = ' ' THEN
            INC (Spaces);           (* space counter *)
        ELSE
            INC (Chars, ChrWid);    (* pixels needed for letters *)
        END;
        INC (Index);
    END;

    IF Spaces > 0 THEN
        Between := (Pixels - Chars) DIV Spaces;     (* calculate spacing *)
        SetColour (InWin.MnuCol, InWin.BckCol)
    END;

    Index := 0;
    WHILE (Index <= HIGH (String)) & (String [Index] # 0X) DO
        Letter := String [Index];
        CASE Letter OF
            ' ' :   INC (InWin.CurX, Between); |
            '_' :   PlotChar (InWin, ' ')       (* Underscore is printed as space *)
        ELSE
            PlotChar (InWin, Letter)            (* letters are printed as such *)
        END;
        INC (Index)
    END
END Center;

PROCEDURE ClickBar (InWin : WinData; String : ARRAY OF CHAR; VAR ClickPoints : ARRAY OF CARDINAL);

(*  Center a line of text in InWin box and mark the clickpoints for the mouse.  *)

VAR Pixels, Chars, Spaces, Count,
    Index, Between                  : CARDINAL;
    Letter                          : CHAR;

BEGIN
    Pixels := InWin.Width - 2 * InWin.Indent - InWin.CurX;
    Spaces := 0;
    Chars  := 0;
    Index  := 0;
    WHILE (Index <= HIGH (String)) & (String [Index] # 0X) DO
        Letter := String [Index];
        IF Letter = ' ' THEN
            INC (Spaces);           (* space counter *)
        ELSE
            INC (Chars, ChrWid);    (* pixels needed for letters *)
        END;
        INC (Index);
    END;

    Between := (Pixels - Chars) DIV Spaces;     (* calculate spacing *)
    SetColour (InWin.MnuCol, InWin.BckCol);

    Index := 0;
    Count := 0;
    WHILE (Index <= HIGH (String)) & (String [Index] # 0X) DO
        Letter := String [Index];
        CASE Letter OF
            '_' :   PlotChar (InWin, ' ');  |   (* Underscore is printed as space *)
            ' ' :   IF Count > 0 THEN
                        ClickPoints [Count] := InWin.CurX;
                        INC (Count);
                        ClickPoints [Count] := InWin.CurY;
                        INC (Count);
                    END;
                    INC (InWin.CurX, Between);
                    ClickPoints [Count] := InWin.CurX;
                    INC (Count);
                    ClickPoints [Count] := InWin.CurY;
                    INC (Count);
        ELSE
            PlotChar (InWin, Letter);           (* letters are printed as such *)
        END;
        INC (Index);
    END;
END ClickBar;

PROCEDURE MakeBox (InWin : WinData);

(*  Make a box on screen starting at (TopX, TopY).  *)

BEGIN
    InWin.CurX := 0;
    InWin.CurY := 0;                        (* Make sure pointers are correct *)
    InWin.DeltaX := InWin.Width - 1;
    InWin.DeltaY := InWin.Height - 1;       (* setup parameters for drawing lines *)
    SetColour (InWin.BoxCol, InWin.BckCol);
    DrawH (InWin, TRUE);        (* draw horizontal line *)
    DrawV (InWin);              (* draw vertical line   *)
    InWin.CurX := 0;
    InWin.CurY := 1;            (* adjust coordinates   *)
    DrawV (InWin);              (* draw last vertical line  *)
    DEC (InWin.CurY);
    INC (InWin.CurX);           (* adjust coordinates once more *)
    DrawH (InWin, TRUE);        (* draw final line  *)
END MakeBox;

PROCEDURE EraseBox (InWin : WinData);

(*  Fill inside of the InWin box with BLACK.    *)

VAR k       : CARDINAL;

BEGIN
    SetColour (WHITE, black);
    InWin.DeltaX := InWin.Width - 2;    (* Do not erase window borders  *)
    InWin.CurY := 0;                    (* prime Y coordinate *)
    FOR k := 3 TO InWin.Height DO
        InWin.CurX := 1;                (* prepare to ... *)
        INC (InWin.CurY);
        DrawH (InWin, FALSE);           (* ... UNdraw a line *)
    END;
END EraseBox;

PROCEDURE FillBox (InWin : WinData);

(*  Fill inside of the InWin box with BckCol.    *)

VAR k       : CARDINAL;

BEGIN
    SetColour (InWin.BckCol, InWin.BckCol);     (* do not overwrite window borders  *)
    InWin.DeltaX := InWin.Width - 2 ;
    InWin.CurY := 0;
    FOR k := 3 TO InWin.Height DO
        InWin.CurX := 1;                (* fill up by ...   *)
        INC (InWin.CurY);
        DrawH (InWin, TRUE);            (* ... drawing lines in succession. *)
    END;
END FillBox;

PROCEDURE WriteNumber (VAR InWin : WinData; Num, Len : CARDINAL);

(*  Print Num right justified in Len positions. *)

VAR Digit   : CHAR;                     (* Most recent digit    *)
    n       : CARDINAL;                 (* Loop counter         *)
    Store   : ARRAY [0..8] OF CHAR;     (* Store result         *)

BEGIN
    FOR n := 0 TO Len DO
        Store [n] := ' ';       (* Clear character buffer   *)
    END;
    n := Len;                   (* Start from the back      *)
    REPEAT
        Digit := CHR ((Num MOD 10) + ORD ("0"));                (* compose numeral  *)
        DEC (n);                                                (* prepare index    *)
        Store [n] := Digit;                         (* temporaly store numeral      *)
        Num := Num DIV 10;                          (* prepaer number for next try  *)
    UNTIL (Num = 0) OR (n = 0);                     (* until ready or string full   *)
    FOR n := 0 TO Len - 1 DO
        PlotChar (InWin, Store [n]);        (* and now plot the result to screen    *)
    END;
END WriteNumber;
   

Module initialisation

What follows is the initialisationcode for VGAlib3. This part is executed whenever there is a line like:

   IMPORT     VgaLib3;

   FROM       VgaLib3    IMPORT .... ;
   
is encountered. This routine checks for available memory, allocated memory if enough present, tries to read the bitmapped tokens and does so if they can be found.
BEGIN
    IF NOT Available (4096) THEN                (* enough memory for BitMap data?   *)
        WriteString ("Insufficient memory to run this program.");
        Terminate (1);
    END;
    ALLOCATE (BitMap, 4096);                (* if so, grab it *)
    Lookup (fp, "bitmap.vga", FALSE);       (* is the pixeldata file at hand?   *)
    IF fp.res = notdone THEN
        WriteString ("Characterset datafile not present.");
        Terminate   (2);                    (* if not, get out with errormessage    *)
    END;
    ReadNBytes (fp, BitMap, 4096, Bread);   (* else read data into BitMap buffer    *)
    IF Bread # 4096 THEN
        WriteString ("Wrong size of BITMAP.VGA file.");
        Terminate   (3);                    (* unless file is too short!    *)
    END;
    Close (fp);             (* That's all folks!    *)
END VgaLib3.
   
You see? Not quite difficult. It's a lot of text, but you will learn a few new tricks and some more background. In an another file (SOAP) I have published about an application for these routines.

Page created 1998 and