[Back to GRAPHICS SWAG index] [Back to Main SWAG index] [Original]

    (*
    PART 1 OF NEWGRAPH.PAS
    APPEND NEWGRPH2.PAS TO THE BOTTOM OF THIS FILE AND SAVE THE
    COMBINED FILES AS NEWGRAPH.PAS - COMPILE NEWGRAPH.PAS AND
    NOW SEE HOW MUCH FASTER AND MORE COMPLETE IT IS THAN ALL OF
    THE OTHER SWAG GRAPHIC & SPRITE UNITS.

    **************************************************************
    NEWGRAPH! The (now slightly outdated) 320 x 200 x 256 VGA MODE
    SUPPORT UNIT by Scott Tunstall (C) 1994, 1996. (Rev 1. created
    in 1994, Final rev. Sept 1995)

    Next project : This package converted to support VESA 16.7
                   Million Colour graphic modes. (That'll be a
                   task and a half)

    After that   : Sleep for a year!!!

    **************************************************************

    READ THE DISCLAIMER FIRST BEFORE DOING ANYTHING!!!

    Purpose of unit
    ---------------
    The purpose of this unit is to provide an all-in-one package to
    allow you to write FAST games in Turbo Pascal.

    The unit incorporates :

             o Easy bitmap initialisation and manipulation routines

             o The fastest masked/unmasked/clipped sprite graphics
               routines you will EVER see for a 386/486 processor.

             o Easy to use palette routines (Not as many as I would
               have liked to have included but there are 100s of
               them available in the public domain - feel free to
               use em if ya like.)

             o Font load/save/display routines which are also the
               fastest you'll see (in 1994).

             o Versatile PCX load routines which can handle page sizes
               up to 320 x 200 (Handy for grabbing sprites.)

    ALL time critical routines (i.e. Sprite drawing, Bitmap copying)
    are written in 100% assembly language and have all been tested
    extensively. (Yes Ronny I did write the assembler)

    So in other words your machine shouldn't bomb when you use this unit!
    (See Disclaimer)

    Any drawbacks ?

    Err.. unfortunately (due to the limitations of Pascal's 286
    restrictions) you can't have a bitmap that exceeds 64K - yes
    I know this sucks but huge pointers don't exist in Pascal!!

    The speed in some areas isn't as fast as it could be.. shit!!
    So, I am considering writing a version of this unit which does
    not use standard Pascal "stack frames" (Where Procedure parameters are
    moved to) but instead requires registers to be set on entry (about
    100% faster).

    But this will all be done once me B.Sc is over.

    THE DISCLAIMER
    --------------

    Scott Tunstall (Me), the programmer of this pascal source and hence unit
    cannot be held responsible if ANY damage, be it physical or otherwise, to
    your system/peripherals etc. occurs from use/misuse of the code
    and/or unit. (Not that this unit uses any system-unfriendly hack
    tricks..)

    You can distribute this unit UNALTERED and it would be nice if you
    mentioned me in any software you create with this unit.

    Feel free to add parts to the unit. If any good, please post em to the SWAG
    and let everyone see them. However, I would prefer to see ASM stuff be added
    instead of plain vanilla pascal.

    Name    : Scott Tunstall
    Address : 40 leadside crescent, Fife, Scotland.

    Minimum System requirements
    ---------------------------
    Turbo Pascal 6 - (Mind and check some of the "switches" below ).
    TP7 recommended though.

    386 processor.
    VGA graphics card that supports mode 13h and the 262,144
        colour palette.

    CONTACT: CG93SAT@IBMRISC.DCT.AC.UK (Up till June 15 1996)
    *)

    { You may have to remove some of these switches if using TP6.
      Turbo 7 really is the bees knees (?) when it comes to software
      development, laddie.
     }

    {$A+,B-,E+,F-,G+,N+,Q-,R-,S-}

    UNIT NEWGRAPH;

    INTERFACE

    Const
          GetMaxX                 = 319;    { Maximum X & Y coordinates }
          GetMaxY                 = 199;
          GetMaxColour             = 255;
          MaxColours               = 256;

          Int1fFont               = 0;
          Int43Font               = 1;
          StandardVGAFont         = 1;
          Font8x8                 = 1;      { Why do I get a "Constant Out
                                            of range error" with this ? }
          Font8x14                = 2;
          Font8x8dd               = 3;      { Abbreviated }
          Font8x8ddHigh           = 4;
          AlphaAlternateFont      = 5;
          FontAlpha               = 5;
          Font8x16                = 6;
          Font9x16                = 7;      { This doesn't appear, though }
          FontRomAlt              = 7;      { it may just be my VGA }

    {
    This record is used to hold a screen/PCX's palette.
    }

    TYPE
    PaletteType = record
       RedLevel:   Array[0..MaxColours-1] of byte;
       GreenLevel: Array[0..MaxColours-1] of byte;
       BlueLevel:  Array[0..MaxColours-1] of byte;
    end;

    {
    This record is used to hold a Font's details, if you didn't guess
    that already ;-)
    }

    FontType = record
       FontSeg       : Word;           { Where Font is located }
       FontOfs       : Word;
       FontWidth     : Byte;           { Width (In Pixels) }
       FontByteWidth : Byte;           { Pixel width divided by 8 }
       FontHeight    : Byte;           { Height (In Pixels) }
       FontChars     : Byte;           { Number of characters in Font }
    End;

    { Jump into Mode 13h }

    Procedure InitVGAMode;

    {
    Bitmap initialisation and manipulation routines.
    }

    Procedure Bitmap(Var BmapSegment,BmapOffset:word);
    Procedure FreeBitmap(BmapSegment,BmapOffset:word);
    Procedure ShowBitmap(BmapSegment,BmapOffset:word);
    Procedure GetSourceBitmapAddr(VAR SourceSeg,SourceOfs: word);
    Procedure SetSourceBitmapAddr(NewSourceSeg,NewSourceOfs:word);
    Procedure GetDestinationBitmapAddr(VAR DestinationSeg,DestinationOfs: word);
    Procedure SetDestinationBitmapAddr(NewDestinationBitmapSeg,NewDestinationBitmapOfs:word);
    Procedure CopySourceBitmap;
    Procedure OverlaySourceBitmap;
    Procedure DoubleBufferOff;

    { Drawing primitives }

    Procedure PutPixel(x, y : integer; ColourValue : Byte);
    Function  GetPixel(X,Y: integer): integer;
    Procedure Line(X1, Y1, X2, Y2:integer);
    Procedure LineRel(DiffX,DiffY: integer);
    Procedure LineTo(Endx,Endy:integer);
    Procedure Rectangle(x1,y1,x2,y2:integer);
    Procedure MoveTo(NewCursX,NewCursY:integer);
    Function  GetX: integer;
    Function  GetY: integer;
    Procedure OutTextXY(x,y:integer; txt:string);
    Procedure OutText(txt:string);

    { Palette stuff }

    Procedure SetColour(NewColour:byte);
    Function  GetColour: byte;
    Procedure GetPalette(ColourNumber : Byte; VAR RedValue, GreenValue, BlueValue : Byte);
    Procedure SetPalette(ColourNumber, RedValue, GreenValue, BlueValue : Byte);
    Procedure LoadPalette(FileName: String; Var Palette : PaletteType);
    Procedure SavePalette(FileName: String; Palette : PaletteType);
    Procedure GetAllPalette(Var Palette : PaletteType);
    Procedure SetAllPalette(Palette : PaletteType);

    {
    Fast sprite (shape) routines.
    }

    Procedure GetAShape(x1,y1,x2,y2:word;Var DataPtr);
    Procedure FreeShape(DataPtr:pointer);
    Procedure Blit(x,y:word; Var DataPtr);
    Procedure ClipBlit(x,y:integer; Var DataPtr);
    Procedure Block(x,y:word; Var DataPtr);
    Procedure ClipBlock(x,y:integer; Var DataPtr);
    Function  BlitColl(x,y :integer; Var dataptr) : boolean;
    Function  ShapeSize(x1,y1,x2,y2:word):word;
    Function  ExtShapeSize(ShapeWidth, ShapeHeight : byte): word;
    Function  ShapeWidth(Var DataPtr): byte;
    Function  ShapeHeight(Var DataPtr): byte;
    Procedure LoadShape(FileName:String; Var DataPtr:Pointer);
    Procedure SaveShape(FileName:string; DataPtr:Pointer);

    {
    Custom Font routines. Unfortunately, I don't know how to load
    in Windows bitmapped Fonts which is a real bast..

    }

    Procedure UseFont(FontNumber:byte);
    Function  GetROMCharOffset(CharNum:byte): word;
    Procedure GetCurrentFontAddr(VAR FontSeg,FontOfs:word);
    Procedure SetCurrentFontAddr(NewFontSeg,NewFontOfs:word);
    Procedure GetCurrentFontSize(Var CurrFontWidth, CurrFontHeight:byte);
    Procedure SetCurrentFontSize(NewFontWidth, NewFontHeight:byte);
    Procedure LoadFont(FontFileName:String; Var FontRec: FontType);
    Procedure UseLoadedFont(FontRec : FontType);
    Procedure SaveFont(FontFileName:String; FirstChar, Numchars:byte);

    {
    Can't include a GIF loader.. Compuserve don't like people using
    their GIF datatype without paying a small fee.. :(
    }

    Procedure LoadPCX(FileName:string; Var ThePalette: PaletteType);
    Procedure LocatePCX(filename:string; Var ThePalette: PaletteType;
              x,y,widthtoshow,heighttoshow:word);
    Procedure SavePCX(filename:string;ThePalette: PaletteType);
    Procedure SaveAreaAsPCX(filename:string;ThePalette: PaletteType;
              x,y, PCXWidth,PCXHeight: word);

    {
    Miscellaneous useful routines.
    }

    Procedure Vwait(TimeOut:word);
    Procedure Cls;
    Procedure CCls(TheColour : byte);

    IMPLEMENTATION

    Uses CRT,Dos;

    {
    This ** structure ** was nicked from READPCX.PAS that's currently
    in the SWAG. Credit to Norman Yen for writing a PCX loader program,
    it was very useful for understanding the PCX compression.

    But my version of the PCX loader (rewritten from scratch) is faster
    (and better) than Norm's effort. And what's more it can handle Mode 13h
    PCX's of any size up to 320 x 200 pixels.

    }

    type Pcxheader_rec=record               { EXPECTED VALUES / COMMENTS}
                                            { --------------------------}
         manufacturer: byte;                { 10. (Why does Z-Soft have
                                              this field ?) }
         version: byte;                     { 5. }
         encoding: byte;                    { 0.  (RLE PCX encryption) }
         bits_per_pixel: byte;              { 8.  (8 bits = 256 colours) }
         xmin, ymin: word;                  { 0,0 (Top Left) }
         xmax, ymax: word;                  { 319,199 (Bottom right) }
         hres: word;                        { 320 (although this (and vres)
                                              may be ignored by some
                                              programs)}
         vres: word;                        { 200 }
         palette: array [0..47] of byte;    { Don't use }
         reserved: byte;                    { Don't use }
         colour_planes: byte;               { 0 (Mode 13h is not planar) }
         bytes_per_line: word;              { 320 (usually, may differ -
                                              although I hear this should
                                              be an even number my PCX load
                                              /save routines work with odd
                                              numbers too) }
         palette_type: word;                { 12 (to work with this unit) }
         filler: string[58];                { Don't know the purpose of this,
                                              could it be for comments etc ? }
    end;

    {
    ****************
    Variable section
    ****************

    Note : You could make these public variables and that would probably
    increase the speed of your programs as you can access the data
    directly (via assembler, for example) instead of using the
    Setxxx() Procedures.
    }

    Var
        SourceBitmapSegment:          word;
        SourceBitmapOffset:           word;
        DestinationBitmapSegment:     word;
        DestinationBitmapOffset:      word;

        CurrentFontSegment:         word;
        CurrentFontOffset:          word;
        CurrentFontWidth:           byte;
        CurrentFontByteWidth:       byte;
        CurrentFontHeight:          byte;
        CurrentColour:               byte;
        CursorX:                    integer;
        CursorY:                    integer;

        header:                     Pcxheader_rec;

    (*
    This routine has nothing to do with graphics - it just helps
    with some routines.

    Expects : PT is a standard pointer.
              Segm and Offs are uninitialised word variables.

    Returns : On exit Segm holds the segment part of the pointer
              Offs holds the offset.

    Corrupts : AX,BX,DI,ES.

    *)

    Procedure GetPtrData(pt:pointer; VAR Segm, Offs:word); Assembler;
    Asm
       LES DI,PT            { Point ES:DI to where PT is in memory }
       MOV AX,ES            { Set AX to hold segment }
       MOV BX,DI            { BX to hold offset }

       LES DI,Segm          { Now write directly to variable Segm }
       MOV [ES:DI],AX
       LES DI,Offs          { And variable Offs }
       MOV [ES:DI],BX
    End;

    {
    Switch into VGA256 (320 x 200 x 256 Colour mode).

    Expects : Nothing

    Returns : Nothing

    Affects : It affects the current screen mode (obviously) palette,
              Font (and the weather in eastern Czechoslovakia :-) )

    Notes  : If all you want to do is clear the screen then use
             Cls or CCls, which does not affect palettes etc.
    }

    Procedure InitVGAMode; Assembler;
    asm
       XOR AH,AH
       MOV AL,$13   { Mode 19 is the mode we want ! ;-) }
       INT $10      { VGA 256 Colours here we come }
    End;

    {
    ****************************
    BITMAP MANIPULATION ROUTINES
    ****************************
    }

    (*
    Allocate memory for a virtual screen. (This command
    it is ALWAYS 64,000 bytes that are allocated - the same
    size as what is used by the VGA bitmap.

    Expects  : Two empty variables of word size which will be
               used to hold the segment and offset of the virtual
               screen.

    Returns  : The segment and offset of the memory area.

    Corrupts : Don't know (and don't care! ).

    Notes    : Unfortunately Pascal doesnt allow allocation of
               > 64K or incorportate HUGE pointers so therefore
               it was made impossible for me to have a huge bitmap
               that exceeds 64K.

    *)

    Procedure Bitmap(Var BmapSegment,BmapOffset:word);
    Var MemoryAccessVar: pointer;
    Begin
         GetMem(MemoryAccessVar,64000);
         GetPtrData(MemoryAccessVar,BmapSegment,BmapOffset);
    End;

    (*
    This routine will free a virtual screen allocated by the
    Bitmap routine above.

    Expects :  The variables passed in as BmapSegment, BmapOffset should hold
               the same contents as what was allocated by Bitmap;

    Returns :  Your machine may crash if you try and free a Bitmap that has
               not been allocated !

    Corrupts : Don't know which registers are altered.

    *)

    Procedure FreeBitmap(BmapSegment,BmapOffset:word);
    Var ThePointer: pointer;
    Begin
         ThePointer:=Ptr(BmapSegment,BmapOffset);
         FreeMem(ThePointer,64000);
    End;

    {
    Procedure used to blit one bitmap to another bitmap. Private
    to unit.

    Expects : DS:SI points to source page
              ES:DI points to destination page
              DX holds data segment address

    Corrupts : CX,SI,DI.

    Returns : Nothing

    }

    Procedure FastCopy; Assembler;
    Asm
         MOV CX,2000
         CLD

         { The reason I have repeated the instructions 8 times is because
         this method is a lot faster than :

    @Copy:
         DB $F3,$66,$a5
         LOOP @Copy

         If you are a total speed junkie then why not block copy those
         8 instructions, append them at the bottom, and set CX (Above)
         to 1000. In fact, for total speed freaks why not type 16,000
         of these instructions :-)

         Alternatively, buy a Pentium 120. ;-)

         (Feb 96 update: No point in me cracking that joke now when
         Melv's got a P133 - how fast technology advances eh?)
         }

    @Copy:
         DB $66; MOVSW      { MOVSD }
         DB $66; MOVSW
         DB $66; MOVSW
         DB $66; MOVSW
         DB $66; MOVSW
         DB $66; MOVSW
         DB $66; MOVSW
         DB $66; MOVSW      { 32 bytes moved in one loop. Whoa !}
         DEC CX
         JNZ @Copy          { On my 486 this is faster than LOOP }

         MOV DS,DX
    End;

    {
    Copy a bitmap in memory to the VGA memory, therefore showing it
    on screen.

    Expects  : BmapSegment, BmapOffset to point to a bitmap in memory.

    Returns  : Nothing

    Corrupts : AX,CX,DX,SI,DI,ES
    }

    Procedure ShowBitmap(BmapSegment,BmapOffset:word); Assembler;
    Asm
       MOV DX,DS
       MOV AX,$a000
       MOV ES,AX
       XOR DI,DI
       MOV SI,BmapOffset
       MOV DS,BmapSegment
       CALL FastCopy
    End;

    (*
    This copies the Source Bitmap to the Destination Bitmap. Simple as that.
    If the Destination Bitmap resides at $a000 : 0 then the VGA screen will
    be updated (The main purpose for this routine)

    Expects : Source Bitmap & Destination Bitmap to point to two legal 64K
              regions of memory (By "legal" I mean you have reserved these
              regions in the program for your own use, or know that they
              are free)

    Returns : Nothing.

    Corrupts : CX,DX,DI,ES
    *)

    Procedure CopySourceBitmap; Assembler;
    Asm
         MOV DX,DS
         MOV ES,DestinationBitmapSegment
         MOV DI,DestinationBitmapOffset
         MOV SI,SourceBitmapOffset
         MOV DS,SourceBitmapSegment
         CALL FastCopy
    End;

    {
    Get the segment and offset of the source Bitmap. (Where data
    is written to, i.e. Sprites, Lines, etc)

    Expects : SourceSeg and SourceOfs are two uninitialised word variables

    Returns : On exit from this routine SourceSeg shall hold the segment and
              SourceOfs shall hold the offset.

    Corrupts : AX,BX,ES

    Notes :    The value on unit initialisation is: Segment = $a000
                                                    Offset  = 0.

    You can change the Source Bitmap address by using SetSourceBitmapAddr.

    }

    Procedure GetSourceBitmapAddr(VAR SourceSeg,SourceOfs: word); Assembler;
    Asm
       MOV AX,SourceBitmapSegment
       MOV BX,SourceBitmapOffset
       LES DI,SourceSeg
       MOV [ES:DI],AX
       LES DI,SourceOfs
       MOV [ES:DI],AX
    End;

    {
    Set the Source Bitmap address. The source Bitmap is where ALL of the
    graphics operations are performed, except for copying.

    Expects : NewSourceSeg = Segment of the new Source Bitmap
              NewSourceOfs = Offset of the new Source Bitmap

    Returns : Nothing

    Notes   : The source Bitmap must reside within the first 640K of DOS memory,
              or at segment $a000 (Video Ram).

              I am sorry about this limitation but that's MS-DOS for you.
              And before a lot of mail floods in saying "what about using XMS"
              etc. I say, "It's in my new unit, old chap" :-)

    Corrupts : AX
    }

    Procedure SetSourceBitmapAddr(NewSourceSeg,NewSourceOfs:word); Assembler;
    Asm
         MOV AX,NewSourceSeg
         MOV SourceBitmapSegment,AX
         MOV AX,NewSourceOfs
         MOV SourceBitmapOffset,AX

    End;

    {
    Get the address of the Destination Bitmap. (Where data is to be copied
    to with CopySourceBitmap).

    Expects : Two word variables to hold the segment & offset of the
              source Bitmap.

    Returns : Segment & Offset of the source Bitmap.

    Corrupts : AX,DI,ES.

    Note : The Destination Bitmap defaults to segment $a000 offset 0.

    }

    Procedure GetDestinationBitmapAddr(VAR DestinationSeg,DestinationOfs: word); Assembler;
    Asm
       MOV AX,DestinationBitmapSegment
       LES DI,DestinationSeg
       MOV [ES:DI],AX
       MOV AX,DestinationBitmapOffset
       LES DI,DestinationOfs
       MOV [ES:DI],AX
    End;

    {
    Set the address of the Destination Bitmap.

    Expects :  NewDestinationBitmapSeg is the segment of the New
               Destination Bitmap. (Never! :-) )
               NewDestinationBitmapOfs is the offset.

    Returns :  Nothing

    Corrupts : AX

    }

    Procedure SetDestinationBitmapAddr(NewDestinationBitmapSeg,NewDestinationBitmapOfs:word); Assembler;
    Asm
       MOV AX,NewDestinationBitmapSeg
       MOV DestinationBitmapSegment,AX
       MOV AX,NewDestinationBitmapOfs
       MOV DestinationBitmapOffset,AX
    End;

    {
    By setting the Destination Bitmap to the Source Bitmap, "double buffering"
    is effectively turned OFF. This routine is only of use to those who
    work with multiple graphics Bitmaps.

    This will make sure that data is written to the Destination
    Bitmap ALWAYS.

    Expects : Nothing.

    Returns : DestinationBitmap points to SourceBitmap.

    Corrupts : AX
    }

    Procedure DoubleBufferOff; Assembler;
    Asm
       MOV AX,SourceBitmapSegment
       MOV DestinationBitmapSegment,AX
       MOV AX,SourceBitmapOffset
       MOV DestinationBitmapOffset,AX
    End;

    {
    This routine will overlay the SOURCE Bitmap with the DESTINATION
    Bitmap (writing the overlaid Bitmap data to the DESTINATION screen)
    therefore making it possible to create a parallaxing
    effect.

    Of course, you could simply use it to overlay two PCXs etc. etc.

    Expects : SourceBitmapSegment, SourceBitmapOffset to point to an
              initialised Bitmap. This Bitmap is treated as the
              FOREGROUND. All pixels with colour 0 within the
              bitmap are treated as TRANSPARENT.

              The same applies to DestBitmapSegment, DestBitmapOffset.
              The Dest Bitmap is treated as the BACKGROUND.

    Returns : Nothing

    Corrupts : AX,CX,DX,SI,DI,ES

    }

    Procedure OverlaySourceBitmap; Assembler;
    Asm
       MOV DX,DS                    { Save DS - faster than using stack }

       MOV DI,DestinationBitmapOffset
       MOV ES,DestinationBitmapSegment
       MOV SI,SourceBitmapOffset
       MOV DS,SourceBitmapSegment
       MOV CX,16000

    @CheckIfTransparent:
       DB $66                       { 66h indicates 32 bit destination }
       LODSW                        { LODSD -> Read DWORD from source Bitmap
                                    into AX }
       OR AL,AL                     { Check if AL is 0 }
       JZ @ALClear                  { If so, can't overlay it }
       MOV [ES:DI],AL               { Otherwise, write it }

    @ALClear:
       INC DI
       OR AH,AH                     { Check if AH is 0 }
       JZ @AHClear                  { Shouldn't blit with a 0 byte }
       MOV [ES:DI],AH

    @AHClear:
       INC DI
       DB $66
       SHR AX,16                    { Move upper word of EAX into
                                      into AH and AL }
       OR AL,AL                     { Check if AL is 0 }
       JZ @EALClear                 { If so, can't overlay it }
       MOV [ES:DI],AL               { Otherwise, write it }

    @EALClear:
       INC DI
       OR AH,AH                     { Check if AH is 0 }
       JZ @NoBlit                   { Shouldn't blit with a 0 byte }
       MOV [ES:DI],AH

    @NoBlit:
       INC DI                       { Next byte }
       DEC CX                       { Reduce count }
       JNZ @CheckIfTransparent

       MOV DS,DX                    { Restore DS }
    End;

    {
    ***********************
    PRIMITIVE DRAWING TOOLS
    ***********************
    }

    {
    Calculate the offset of a pixel on the SOURCE Bitmap.

    Registers expected on entry:
    AX = the horizontal coordinate (0 to GetMaxX) and ..
    BX = the vertical coordinate (0 to GetMaxY)

    Returns  : BX = -1 if X or Y were out of bounds.
               Otherwise, BX is an offset, which, combined with
               the contents of SourceBitmapSegment point to an address
               in RAM where the pixel can be plotted/read from.

    Notes    : This routine is private to the unit. To maintain
               compatibility with further revisions (which I churn out
               with frightening regularity ;-) ) I recommend all extra
               unit routines that require a pixel address calc'ed call
               this proc.

    Corrupts : AX, BX, CX are corrupted.

    }

    Procedure CalculateOffset; Near; Assembler;
    Asm
         CMP AX,319         { Is X> 319 ? }
         JA @OutOfBounds    { Yes }
         CMP BX,199         { Is Y> 199 ?. Do not use BL instead as this is
                              when problems will occur.}
         JA @OutOfBounds    { Yes }

         XOR CH,CH                  { CX = Y }
         MOV CL,BL
         SHL CX,6                   { Y * 64 }
         MOV BH,BL                  { BX = Y * 256 }
         XOR BL,BL
         ADD BX,CX                  { BX = BX + CX, which gives Y * 320 }
         ADD BX,AX                  { Add the X position to offset in BX }
         ADD BX,SourceBitmapOffset    { Take into account the offset in memory
                                      of the source Bitmap }

         JMP @Finito                { And exit. }

    @OutOfBounds:
         MOV BX,-1                  { Signal that coordinates were not within
                                      the screen limits }

    @Finito:
    End;

    {
    This GetPixel routine differs from the Turbo equivalent as the
    return type is integer, not word. A small point, but still
    (UN)worth mentioning. <grin>

    Expects  : X and Y specify the horizontal and vertical coordinates of
               a pixel. X may be 0..GetMaxX, Y may be 0..GetMaxY.

    Returns  : If the coordinates are within screen bounds, then GetPixel =
               Colour at X,Y. If not, then GetPixel = -1.

    Corrupts : AX/BX/CX/DX/FS.
    }

    Function GetPixel(X,Y: integer): integer; Assembler;
    Asm
       MOV AX,X
       MOV BX,Y

       CALL CalculateOffset         { Now get offset in BX }
       CMP BX,-1                    { Is coordinate off screen ? }
       JZ @NoGet                    { Yes, so return value of -1 }
       DB $8E, $26
       DW OFFSET SourceBitmapSegment

       XOR AH,AH
       DB $64
       MOV AL,[BX]

       JMP @Finished                { Can't put a RET here - maybe this
                                      unit was compiled in FAR mode, and
                                      a crash would occur! }

    @NoGet:
       MOV AX,BX                    { AX = -1, meaning no pixel could be
                                      read }

    @Finished:
    End;

    {
    Write a pixel to the screen.

    Expects :  AX to be the X coord for a pixel (0 to GetMaxX),
               BX for the Y coord (0 to GetMaxY) - Don't be tempted
               to optimize the code by using BL, as this causes
               problems when using negative Y coordinates. (As some
               programs will)
               DL is the colour (0 to 255) to plot.

    Returns :  Nothing

    Notes   :  This putpixel is private to the unit and should be
               used when plotting pixels that MAY be off screen
               to keep in step with the rest of the unit.

    On exit AX,BX,CX,DX,FS are corrupt.
    }

    Procedure FPutPixel; Near; Assembler;
    Asm
       CALL CalculateOffset                 { AX/ BX already set up }
       CMP BX,-1                            { Coordinates off screen ? }
       JA @NoPlot                           { Yeah, so don't put pixel }
       DB $8E,$26                           { MOV FS, [SourceBitmapSegment] }
       DW OFFSET SourceBitmapSegment
       DB $64                               { MOV [FS:BX],DL }
       MOV [BX],DL

    @NoPlot:
    End;

    {
    This is the Pascal interface for the Fputpixel routine, it's
    really quite sad how Pascal uses the stack so much, when you see
    the likes of Turbo C & it's (amazingly interesting) register
    usage which is quite fast. :(

    But not as fast as me when I'm going to the pub. :-)

    Expects : X = Horizontal coordinate of a pixel (0-GetMaxX)
              Y = Vertical coordinate of a pixel (0-GetMaxY)
              ColourValue = Colour to plot , 0 - 255.

    Returns : Nothing

    Corrupts : See FPutPixel.
    }

    Procedure PutPixel(x, y : integer; ColourValue : Byte); Assembler;
    Asm
       MOV AX,x               { I wish TP had the capacity to load these
                                automatically for you, instead of creating
                                a crappy stack frame and pushing X, Y. }
       MOV BX,y               { Is it any wonder I love C++ more ? }
       MOV DL,ColourValue
       CALL FPutPixel         { Don't use a JMP, your program will crash }
    End;

    {
    This line routine was converted to assembler (by ME!!) from the
    SWAG team's line draw routine (in Pascal) which was very fast.
    So this means this'll be ULTRA FAST (hopefully ;-) ).

    Bresenham who ? :-)  Diamond Geezer.

    I wonder if this is faster than Sean Palmer's line draw in ASM ?
    (Check the SWAG for that program - it's smart)

    Expects : X1,Y1 defines the horizontal, vertical start of the line
              X2,Y2 defines the horizontal, vertical end of the line.
              Coordinates may be negative or exceed screen bounds.

              Line will be drawn in CurrentColour.

    Returns : Nothing

    Corrupts: AX,BX,CX,DX,SI,DI,ES,FS.

    }

    Procedure Line(X1, Y1, X2, Y2: Integer); Assembler;
    Var
      LgDelta,
      ShDelta,
      LgStep,
      ShStep,
      Cycle : word;

    Asm
      MOV BX,X2             { LgDelta = X2 - X1 }
      MOV SI,X1
      SUB BX,SI
      MOV LgDelta,BX

      MOV CX,Y2             { ShDelta = Y2 - Y1 }
      MOV DI,Y1
      SUB CX,DI
      MOV ShDelta,CX

      TEST BH,$80           { If bit 7 not set .. }
      JZ @LgDeltaPos        { Goto LgDeltaPos }

      NEG BX
      MOV LgDelta,BX
      MOV LgStep,$FFFF
      JMP @Cont1

    @LgDeltaPos:
      MOV LgStep,1

    @Cont1:
      CMP CH,$80           { If ShDelta < 0 Then.. }
      JB @ShDeltaPos
      NEG CX
      MOV ShDelta,CX
      MOV ShStep,$FFFF
      JMP @Cont2

    @ShDeltaPos:
      MOV ShStep,1

    @Cont2:
      CMP BX,CX                   { BX = LgDelta, CX = ShDelta }
      JB @OtherWay

      SHR BX,1                    { Cycle:= LgDelta SHR 1 }
      MOV Cycle,BX

      {
      O.K. I'm going to use :
      SI as X1, DI as Y1, CX as X2, DX as Y2.
      }

      MOV CX,X2

    @FirstLoop:
      CMP SI,CX             { While X1 <> X2 }
      JZ @GetTheShitOut     { Why not have an expletive as a label ? }

      MOV AX,SI              { Set AX and BX to X1,Y1 ready for call }
      MOV BX,DI              { BX = Y1 }

      MOV ES,CX              { The only free register ! }
      MOV DL,CurrentColour
      CALL FPutPixel
      MOV CX,ES

      ADD SI, LgStep         { X1 = X1 + LgStep }
      MOV AX,Cycle
      ADD AX,ShDelta         { Inc(Cycle,ShDelta) }
      MOV Cycle,AX           { Yes I did check the code and this is fastest }

      MOV BX,LgDelta
      CMP AX,BX              { If Cycle > LgDelta }
      JB @FirstLoop

      ADD DI,ShStep          { Y1 = Y1 + ShStep }
      SUB AX,LgDelta         { Dec(Cycle,LgDelta) }
      MOV Cycle,AX
      JMP @FirstLoop

      {
      O.K. If we go in a different direction..

      On entry, BX = LgDelta, CX = ShDelta

      }

    @OtherWay:
      MOV AX,CX
      SHR AX,1              { ShDelta SHR 1 }
      MOV Cycle,AX
      XCHG BX,CX            { BX = ShDelta, CX = LgDelta }
      MOV LgDelta, BX
      MOV ShDelta, CX

      MOV BX,LgStep         { Swap LgStep and ShStep round }
      MOV CX,ShStep
      MOV ShStep,BX
      MOV LgStep,CX

      {MOV CX,X2}             { CX = X2, DX = Y2 }
      MOV DX,Y2

    @SecondLoop:
      CMP DI,DX             { While Y1 <> Y2 do }
      JZ @GetTheShitOut

    {
    If it can, then it's time for action!
    }

      MOV AX,SI             { Set AX and BX to X1,Y1 }
      MOV BX,DI             { BX = Y1 }

      MOV ES,DX             { Sorry, but this was the only free register ! }
      MOV DL,CurrentColour
      CALL FPutPixel
      MOV DX,ES             { .. Please don't think I am sloppy ! }

      ADD DI,LgStep         { Inc(Y1,LgStep) }
      MOV AX,Cycle          { Inc(Cycle,ShDelta) }
      ADD AX,ShDelta
      MOV Cycle,AX

      MOV BX,LgDelta

      CMP AX,BX             { If Cycle > LgDelta Then.. }
      JB @SecondLoop

      ADD SI,ShStep         { Inc(X1,ShStep) }
      SUB Cycle,BX          { Dec(Cycle,LgDelta) }
      JMP @SecondLoop

    @GetTheShitOut:
      MOV AX,X2             { Write last pixel. This was an absolute }
      MOV BX,Y2             { b****** to debug :-) }
      MOV DL,CurrentColour
      CALL FPutPixel        { Just a wee bit of Scottish humour there }

    End;

    {
    Draw a line relative from the current cursor position.
    Relative means that the DiffX and DiffY values are added to the
    current cursor coordinates to give the resulting horizontal and vertical
    end points of the line.

    For example, if CursorX and CursorY were 10,10 and DiffX and DiffY
    were -10,-10 then the line would be drawn to position 0,0. Conversely,
    if DiffX was 10 and DiffY was 20 then the cursor would be drawn to
    X 20, Y 30.

    Expects : DiffX is a non zero value that may be negative, which
              specifies the relative distance from the current horizontal
              cursor position.

              DiffY specifies the relative distance from the current
              vertical position.

    Returns : Nothing

    Corrupts : Probably the same as the Line routine.
    }

    Procedure LineRel(DiffX,DiffY: integer); Assembler;
    Asm
         MOV AX,CursorX
         MOV BX,AX
         ADD BX,DiffX
         MOV CX,CursorY
         MOV DX,CX
         ADD DX,DiffY

         {
         Strange method of reading the stack, Borland. :-(
         }

         PUSH BX            { X + DiffX }
         PUSH DX            { Y + DiffY }
         PUSH AX            { X }
         PUSH CX            { Y }
         CALL Line          { Must return so dynamic vars can be moved.
                              Wish I could get rid of them quicker. }
    End;

    {
    Draw from the current cursor position to the horizontal and vertical
    positions specified by EndX and EndY. The Graphics Cursor will be
    moved to EndX, EndY.

    Expects : EndX to be the horizontal position of the line end. (0 to GetMaxX)
              EndY to be the vertical position of the line end. (0 to GetMaxY)

    Returns : Nothing, but you should be aware that the graphics cursor
              position is now at EndX, EndY.

    Corrupts : AX,BX,CX,DX,SI,DI,ES,FS
    }

    Procedure LineTo(EndX,EndY:integer); Assembler;
    Asm
       PUSH EndX
       PUSH EndY
       PUSH CursorX
       PUSH CursorY
       CALL Line
       MOV AX,EndX
       MOV CursorX,AX
       MOV AX,EndY
       MOV CursorY,AX
    End;

    {
    Probably not the fastest rectangle draw you'll see.
    But it is economical with memory, and it works !

    Expects  : X1,Y1,X2,Y2 define a rectangular window.

    Returns  : Nothing

    Corrupts : Not a clue.

    Notes    : This routine does not move the graphics cursor.
    }

    Procedure Rectangle(x1,y1,x2,y2:integer);
    Begin
         Line(x1,y1,x2,y1);         { Top Line    }
         Line(x1,y2,x2,y2);         { Bottom Line }
         Line(x1,y1+1,x1,y2-1);     { Left edge   }
         Line(x2,y1+1,x2,y2-1);     { Right edge  }
    End;

    {
    Change position of graphics cursor.

    Expects : NewCursX and NewCursY are the horizontal and vertical
              coordinates that you wish to move the cursor to.
              NewCursX may be negative or more than GetMaxX.
              NewCursY may be negative or more than GetMaxY.

    Returns : Nothing

    Corrupts : AX.
    }

    Procedure MoveTo(NewCursX,NewCursY:integer); Assembler;
    Asm
       MOV AX,NewCursX
       MOV CursorX,AX
       MOV AX,NewCursY
       MOV CursorY,AX
    End;

    {
    Returns horizontal position of graphics cursor.
    GetX May be negative.

    Expects : Nothing

    Returns : GetX = Current graphics cursor horizontal position, which
              may be negative or even exceed GetMaxX.
    }

    Function GetX: integer; Assembler;
    Asm
       MOV AX,CursorX
    End;

    {
    Returns vertical position of graphics cursor.
    GetY may be negative.

    Expects : Nothing

    Returns : GetY = Current graphics cursor vertical position, which
              may be negative or even exceed GetMaxY.

    }

    Function GetY: integer; Assembler;
    Asm
         MOV AX, CursorY
    End;

    {
    *************
    FONT ROUTINES
    *************

    }

    {
    Select which of the Fonts in ROM you use to write text to the
    screen.

    Expects : FontNumber can be:

              0: For CGA Font (Dunno what size it is tho')
              1: For 8 x 8 Font
              2: For 8 x 14 Font
              3: For 8 x 8 Font
              4: For 8 x 8 Font high 128 characters
              5: For Rom Alpha Alternate Font
              6: For 8 x 16 Font
              7: For Rom Alternate 9 x 16 Font

    Returns : Nothing

    Corrupts : AX,BX,ES

    }

    Procedure UseFont(FontNumber:byte); Assembler;
    Asm
         MOV AX,$1130                      { Get Font address }
         MOV BH,FontNumber
         CMP BH,7                          { Font number > 7 ? }
         JA @NoWriteSize                   { Yes, so it's invalid }

         PUSH BP                           { Mustn't corrupt BP, as Turbo
                                             needs it preserved for local
                                             variable access }
         PUSH BX                           { Nor BH as it's to be used later }
         INT $10                           { Now get Font address }
         MOV CurrentFontSegMent,ES         { ES:BP points to where Font is }
         MOV CurrentFontOffset,BP          { located in ROM }
         POP BX                            { Restore Font number }
         POP BP                            { Restore BP }

         CMP BH,Int1fFont                  { User Font in memory ? }
         JZ @NoWriteSize                   { Don't set size, could be more than
                                             8 x 8. User will have to set himself.
                                             Please correct me if I am wrong }
         CMP BH,Font8x8                    { User want any of the 8 x 8 Fonts ? }
         JZ @Set8x8
         CMP BH,Font8x8dd
         JZ @Set8x8
         CMP BH,Font8x8ddHigh
         JZ @Set8x8
         CMP BH,AlphaAlternateFont
         JNZ @Check8x14Font

    @Set8x8:
         MOV AL,8                          { Width of 8 }
         MOV AH,8                          { Height of 8 }
         MOV BL,1                          { 1 byte's width }
         JMP @DoWrite

    @Check8x14Font:
         CMP BH,Font8x14
         JNZ @Check8x16Font
         MOV AL,8                          { Width 8, Height 14, ByteWidth 1 }
         MOV AH,14
         MOV BL,1
         JMP @DoWrite

    @Check8x16Font:
         CMP BH,Font8x16
         JNZ @UseRomAlternateFont
         MOV AL,8                          { Oh C'mon do I have to document }
         MOV AH,16                         { this ? }
         MOV BL,1
         JMP @DoWrite

    @UseRomAlternateFont:
         MOV AL,9
         MOV AH,16
         MOV BL,2

    @DoWrite:
         MOV CurrentFontWidth,AL           { Write Font details so that }
         MOV CurrentFontByteWidth,BL       { outtextXY etc. can work with }
         MOV CurrentFontHeight,AH          { this Font }

    @NoWriteSize:
    End;

    {
    If you wish to do your own text routines, then this returns the
    address of the current Font in FontSeg and FontOfs which specify the
    segment and offset address of the character set.

    Expects  : Two uninitialised word variables

    Returns  : FontSeg = Segment where Font is located
               FontOfs = Offset of Font

    Corrupts : AX,DI,ES.

    }

    Procedure GetCurrentFontAddr(VAR FontSeg, FontOfs:word); Assembler;
    Asm
       MOV AX,CurrentFontSegment
       LES DI,FontSeg
       MOV [ES:DI],AX
       MOV AX,CurrentFontOffset
       LES DI,FontOfs
       MOV [ES:DI],AX
    End;

    {
    If you want to use a Font loaded in from disk use SetFontAddr to
    specify where the new Font resides in memory.

    Expects : NewFontSeg and NewFontOfs are the segment and offset of the
              address.

    Returns : Nothing

    Corrupts : AX
    }

    Procedure SetCurrentFontAddr(NewFontSeg,NewFontOfs:word); Assembler;
    Asm
       MOV AX,NewFontSeg
       MOV CurrentFontSegment,AX
       MOV AX,NewFontOfs
       MOV CurrentFontOffset,AX
    End;

    {
    Find out what width and height the current Font is.

    Expects: CurrFontWidth and CurrFontHeight are two uninitialised
             variables.

    Returns: CurrFontWidth and CurrFontHeight on exit hold the width
             and height of the current Font. (Bet you never guessed that, huh)

    Corrupts : AX,DI,ES
    }

    Procedure GetCurrentFontSize(Var CurrFontWidth, CurrFontHeight:byte); Assembler;
    Asm
       MOV AL,CurrentFontWidth
       MOV AH,CurrentFontHeight

       LES DI,CurrFontWidth         { ES: DI points to variable now }
       MOV [ES:DI],AL
       LES DI,CurrFontHeight
       MOV [ES:DI],AH
    End;

    {
    Specify width and height of a user created Font.

    Expects  : NewFontWidth must be above 7,
               NewFontHeight can be any non-zero number.

    Returns  : Nothing

    Corrupts : AX

    }

    Procedure SetCurrentFontSize(NewFontWidth, NewFontHeight:byte); Assembler;
    Asm
         MOV AL,NewFontWidth
         MOV AH,NewFontHeight

         CMP AL,8                   { Width >= 8 ? }
         JB @IllegalSize
         OR AH,AH                   { Is Height 0 ? }
         JZ @IllegalSize

         MOV CurrentFontWidth,AL
         MOV CurrentFontHeight,AH
         SHR AL,3                   { Calculate byte width of characters
                                      i.e. divide width in pixels by 8 }
         MOV CurrentFontByteWidth,AL

    @IllegalSize:

    End;

    {
    For those of you who want to do your own text routines, this
    Procedure may lighten your workload a bit.

    Expects : Characternumber to be (obviously) the number of the
              character.

    Returns : This Function returns the offset address of character.

    Corrupts : AX,BX,DX
    }

    Function GetROMCharOffset(CharNum:byte): word; assembler;
    Asm
       MOV AL,CharNum                  { Get number of character into AL }
       MOV BH,CurrentFontByteWidth
       MOV BL,CurrentFontHeight
       MUL BL                          { Multiply character num by FontHeight }
       MOV BL,BH
       XOR BH,BH
       MUL BX                          { And FontWidth }
       ADD AX,CurrentFontOffset        { Now add in the font offset }
    End;

    (*
    This routine lets you load bitmapped Font files (created by this
    unit) from disk. Currently I am examining the file format of
    Compugraphic Fonts and basically I understand absolutely sod all
    of it.. send me some code for reading them please !!

    FontType = record
       FontSeg    : Word;           { Where Font is located; when loaded }
       FontOfs    : Word;           { in these are set by system }
       FontWidth  : Byte;           { Width (In Pixels) }
       FontByteWidth : Byte;
       FontHeight : Byte;           { Height (In Pixels) }
       FontChars  : Byte;           { Number of characters in Font }
    End;

    *)

    Procedure LoadFont(FontFileName:String; Var FontRec: FontType);
    Var FontFile : File;
        BytesToReserve : word;
        FontPtr : Pointer;

    Begin
         Assign(FontFile,FontFileName);
         Reset(FontFile,1);
         BlockRead(FontFile,FontRec,SizeOf(FontRec));
         With FontRec Do
              Begin
              BytesToReserve:=FontChars * (FontByteWidth * FontHeight);
              GetMem(FontPtr,BytesToReserve);
              GetPtrData(FontPtr,FontSeg,FontOfs);
              BlockRead(FontFile,Mem[FontSeg:FontOfs],BytesToReserve);
         End;
         Close(FontFile);
    End;

    {
    This routine will save a portion (or all) of the current Font to disk.

    Expects : FontFileName to be an MS-DOS filename to hold the char data.
              FirstChar to be the number of the first character to save
              (0-255);
              NumChars to be the number of characters to save (You may
              only want to save part of a Font).

    Returns  : Nothing

    Corrupts : Don't know.
    }

    Procedure SaveFont(FontFileName:String; FirstChar, Numchars:byte);
    Var TempFontRec     : FontType;
        FontFile        : File;
        BytesPerChar    : word;
        FirstCharOffset : word;

    Begin
         With TempFontRec do
              Begin
              FontSeg:=0;               { 0 Meaning uninitialised }
              FontOfs:=0;
              FontByteWidth:=CurrentFontByteWidth;
              FontWidth:=CurrentFontWidth;
              FontHeight:=CurrentFontHeight;
              FontChars:=NumChars;
         End;

         Assign(FontFile,FontFileName);
         Rewrite(FontFile,1);
         BlockWrite(FontFile,TempFontRec,SizeOf(TempFontRec));

         BytesPerChar:=CurrentFontByteWidth * CurrentFontHeight;
         FirstCharOffset:=CurrentFontOffset+(FirstChar * BytesPerChar);

         BlockWrite(FontFile, Mem[CurrentFontSegment:FirstCharOffset],
         NumChars * BytesPerChar);

         Close(FontFile);

    End;

    {
    Use a Font loaded in from disk. Yes, I know there are many Font load
    routines in the SWAG and most (if not ALL) use interrupt 10h to do
    the business. But my routine doesn't because quite frankly using the
    BIOS is slow, cack, and is far too limiting.

    This routine allows characters of ANY size.

    Expects : Variable FontRec to have been initialised (usually by LoadFont).
              You could initialise FontRec yourself if you liked and
              that would be faster than using SetFontAddr, SetFontSize etc.

    Returns : Nothing

    Corrupts : Don't know. That's the thing about Pascal!
    }

    Procedure UseLoadedFont(FontRec : FontType);
    Begin
         With FontRec Do
              Begin
              CurrentFontSegment:=FontSeg;
              CurrentFontOffset:=FontOfs;
              SetCurrentFontSize(FontWidth,FontHeight);
         End;
    End;

    {
    Display text at a position on screen. (May be off screen)

    Expects : X,Y specify the top left of where the text is to be
              printed.
              txt is the actual text to be printed.

    Returns : Graphics cursor position is changed. (In normal Turbo
              it is not, but what the hell)

    Corrupts : AX,BX,CX,DX,SI,DI,ES,FS,GS.

    }

    Procedure OutTextXY(x,y:integer; txt:string); Assembler;
    Asm
             MOV AX,X
             MOV CursorX,AX
             MOV AX,Y
             MOV CursorY,AX

             XOR BH,BH                    { Get Font height into BX }
             MOV BL,CurrentFontHeight
             NEG BX                       { Make BX negative number }

             CMP AX,BX                    { Check if text would not be
                                            seen at top edge of screen
                                            (i.e. If -FontHeight >
                                            CursorY) }
             JL @NoWrite                  { Yes, so don't write text }

             CMP AX,199                   { Check if off bottom of screen }
             JG @NoWrite                  { Yes, so don't write text }

             PUSH BP
             LES DI,TXT                   { Yes, I know LGS DI exists but
                                            it's a lot of hassle to find
                                            out it's opcodes !}
             MOV AX,ES
             DB $8E,$E8                   { MOV GS, AX }

             DB $65                       { GS : }
             MOV CL,[DI]                  { MOV CL, [GS:DI]
                                            CL = Length of string }

    @ReadChar:

             INC DI                      { Prepare to read char }
             PUSH DI                     { And offset of char }
             PUSH CX

             DB $65                      { GS : }
             MOV AL,[DI]                 { AL = Character }
             XOR AH,AH

             PUSH AX

             MOV AL,CurrentFontByteWidth   { Now compute Fontbytewidth
                                             * Fontheight }
             MOV BL,CurrentFontHeight

             MUL BL                        { Fontbytewidth * FontHeight }
             MOV DI,AX                     { DI = Result }

             POP AX                        { Restore character number }
             MUL DI                        { AX = Char * (FontByteWidth *
                                             FontHite) }

             ADD AX,CurrentFontOffset
             MOV DI,AX                     { Now DI is correctly placed }

             {
             Now blit the data to the screen
             Come on Bas, write something faster for this purpose..
             Bet you can't !
             }

             MOV ES,CurrentFontSegment

             MOV AX,CursorX              { Update graphic coordinates }
             MOV BX,CursorY

             MOV CH,CurrentFontHeight

    @ScanLineLoop:
             PUSH CX                     { Save Vert Count on stack }
             MOV CH,CurrentFontByteWidth

    @OuterLoop:

             MOV CL,[ES:DI]        { Read byte from charmap }
             OR CL,CL              { test if it's 0 }
             JZ @RestoreByteOffset { If so, no point in wasting CPU time }

             {
             Otherwise..
             }

             MOV BP, AX            { Save X - Coord }
             MOV DH,8              { 8 bits make a character's byte }
             MOV DL,CurrentColour   { FPutPixel needs this }

    @PlotLoop:
             TEST CL,$80           { Bit 7 set ? }
             JZ @NoPlot            { No, so don't plot a pixel }

             MOV SI,AX             { Save X in SI - SI is the only
                                     Free register and it's faster than
                                     a PUSH }

             PUSH BX
             PUSH CX

             CALL FPutPixel        { Plot pixel at AX,BX. }

             POP CX
             POP BX
             MOV AX,SI             { Restore X coord }

    @NoPlot:
             SHL CL,1              { Shift char byte left }
             INC AX                { Adjust X }

             DEC DH                { Reduce horizontal count }
             JNZ @PlotLoop         { If not 0, go to plot loop }

             MOV AX,BP

    @RestoreByteOffset:
             INC DI                { move to next byte }

             DEC CH                { Reduce byte count }
             JNZ @OuterLoop

             POP CX                { Restore vert count }

             INC BX                { Add 1 to Y, assuming Y is not more
                                     than 255. Do NOT use BL to gain more
                                     speed! unexpected side effects will
                                     occur when writing text at the top of
                                     your screen }
             DEC CH                { Reduce vert count }

             JNZ @ScanLineLoop

    {
    Now is the time to update the graphics cursor after the single
    character has been printed.
    }

             MOV AL,CurrentFontWidth
             XOR AH,AH                   { Make AH 0 }
             ADD CursorX,AX              { Update the graphics cursor }

             POP CX                { Restore width. Wish there were more
                                     data registers to work with but there
                                     aren't and it's a bad situation really }
             POP DI                { Restore next char to print's offset }

             DEC CL                { Reduce char length counter }
             JNZ @ReadChar

             POP BP

    @NoWrite:
    End;

    {
    Display a string of text at the current cursor position, using
    the current Font.

    Expects : Txt is the text to write at the current cursor position.

    Returns : Graphics cursor has moved.

    Corrupts : See OutTextXY.
    }

    Procedure OutText(txt:string);
    Begin
         OutTextXY(CursorX,CursorY,txt);
    End;

[Back to GRAPHICS SWAG index] [Back to Main SWAG index] [Original]