
* *** xtext.asm *************************************************************
*
* XText  --  The XText Routine
* (from the FastText algorithms created in January, 1986)
*   from Book 1 of the Amiga Programmers' Suite by RJ Mical
*
* Copyright (C) 1986, 1987, Robert J. Mical
* All Rights Reserved.
*
* Created for Amiga developers.
* Any or all of this code can be used in any program as long as this
* entire copyright notice is retained, ok?  Thanks.
*
* HISTORY       Name             Description
* ------------  ---------------  -------------------------------------------
* 27 Oct 86     RJ Mical >:-{)*  Translated this file.
*
* ***************************************************************************

        INCLUDE "xtext.i"


        IFND    AZTEC ; If AZTEC not defined, do it the standard way
        XDEF    _XText
        XREF    _custom
        XREF    _GfxBase
        ENDC
        IFD     AZTEC ; If AZTEC defined, do it the non-standard way (sigh)
        PUBLIC  _XText
        PUBLIC  _custom
        PUBLIC  _GfxBase
        ENDC



_XText:
* ***************************************************************************
* These FastText algorithms were created by =Robert J. Mical= in January 1986
* Copyright (C) 1986, =Robert J. Mical=
* All Rights Reserved
*
* This is my brute-force XText() routine.
* It presumes many things about text:  that the characters come in
* pairs, that the characters are 8-bits wide (this one is not easily 
* undone), and more.
*
* The theory of operation here is that I will build a single plane of
* normal characters (character data bits set, background bits clear)
* in the Normal Text Plane, using the data from the Output Characters
* Buffer.  As needed, I will invert this plane (character bits clear,
* background bits set) into the Inverted Text Plane.  There's two other
* globally accessible (pre-initialized) planes used:  an AllClearPlane plane
* of all bits clear, and an AllSetPlane plane of all bits set.
*
* Using these four planes, I can construct a bitmap of all possible pen
* settings for the foreground and background.  If corresponding bits
* in the FgPen and BgPen are:
*        -----      -----
*          0       0        Use All Clear Plane
*          0       1        Use Inverted Text Plane
*          1       0        Use Normal Text Plane
*          1       1        Use All Set Plane
*
* Note that if FgPen and BgPen are equal, I don't have to bother
* constructing the Normal or Inverted Text Planes, since they'll
* never be used.
*
* An optimization that evolves out of this fact is that you can fill a line
* or part of a line of your text with spaces (blank characters) much more
* quickly by setting the foreground pen equal to the background pen.  The
* trick with this optimization is to not spend too much time detecting
* whether or not an area of text is all blank, for you may lose the
* increased performance during the handling of normal text lines.
*
* A further optimization is that I'm guessing that many text calls
* will not require the inverted plane, so I won't make it until I 
* discover that I need it.  If programmers follow the
* Intuition-encouraged standard of pen 1 for foreground and pen 0
* for background, then the bits are:  FgPen -- 00001
*                                     BgPen -- 00000
* In fact, if the text is *any* color against a background of zero,
* this optimization works.
*
* For example, the plain PC monochrome text turns out to be pen 1 on
* pen 0.  No need to invert here!
*
* So I won't bother constructing the inverted plane until I discover
* that it's needed.  The cost of this is that I have to check a flag
* once per time that I find an inverted bit plane is called for (6 times 
* maximum (though of course Dale would say 8 times maximum)), and the 
* savings is avoiding an unnecessary inversion.
*
* Note that this routine works with character pairs while building the
* buffer.  If you specify an odd number of characters, this routine will 
* round up the character count to the next higher even number, and then 
* build the buffer with that many characters.  However, only the number
* of characters you specify will actually be printed to the screen.
*
* ON ENTRY (on stack):
*       ARG0 = address of the XTextSupport structure
*       ARG1 = address of the text string
*       ARG2 = character count
*       ARG3 = x position for text output
*       ARG4 = y position for text output

DREGS EQU       0         ; Offset to the pushed D-Registers
DREGCOUNT EQU   6         ; How many D-Registers were pushed
AREGS EQU       (DREGS+(DREGCOUNT*4)) ; Offset to the pushed A-Registers
AREGCOUNT EQU   5
FRONTPEN EQU    (AREGS+(AREGCOUNT*4))   ; Local variable
BACKPEN  EQU    (FRONTPEN+2)    ; Local variable
DRAWMODE EQU    (BACKPEN+2) ; Local variable
XBLTSIZE EQU    (DRAWMODE+2)
RETADDR  EQU    (XBLTSIZE+2)    ; Our return address
ARG0  EQU       (RETADDR+4) ; Offset to passed arguments
ARG1  EQU       (ARG0+4)
ARG2  EQU       (ARG1+4)
ARG3  EQU       (ARG2+4)
ARG4  EQU       (ARG3+4)


        LEA     -8(SP),SP               ; Reserve memory for local variables
        MOVEM.L A2-A6/D2-D7,-(SP)


        MOVE.L  ARG0(SP),A2     ; Get the XTextSupport structure address
        MOVE.L  #_custom,A5     ; Get the address of the custom chip
        MOVE.L  _GfxBase,A6     ; Load up A6 for graphics calls


* Check the character count.
* If it's zero, then split as there's nothing to do.
* If it's less than zero, this is the signal that the programmer
* wants *us* to figure out how long the string is.  Nice touch, eh?
* If it's greater than zero, then presume it's valid.
        MOVE.W  ARG2+2(SP),D2   ; Get the character count
        BEQ     RETURN          ; and split if there's none to do
        BGT     GOT_TEXT_COUNT  ; If greater than zero, then it's real

        MOVE.L  ARG1(SP),A4     ; Address of text string
        MOVEQ.L #-1,D2          ; Start with less than no characters
10$     ADDQ.W  #1,D2           ; Increment character count
        TST.B   (A4)+           ; Test if next is end of text
        BNE     10$             ; Branch if not

        MOVE.W  D2,ARG2+2(SP)   ; Save as character count

GOT_TEXT_COUNT:
* The result of the following 3 instructions is created by the 2 below
* I include this commentary to keep things from getting confusing
*  ADDQ  #1,D2   ; to round up
*  LSR.B #1,D2   ; Turn the char count into a pair count
*  SUBQ.W       #1,D2    ; (-1 for DBRA of PAIRLOOP below)
        SUBQ.B  #1,D2
        LSR.B   #1,D2


        CALLSYS OwnBlitter      ; Get that blitter


* Get local copies of the pens, jazzed around to take the DrawMode into
* account.  Namely, if JAM1 then the background pen is automatically zero,
* and if the INVERSVID flag is set then swap the foreground/background pens

        CLEAR   D3              ; Save them as words
        MOVE.B  xt_FrontPen(A2),D3 ; Load up the local front pen
        CLEAR   D4              ; Start out presuming that back is zero
        MOVE.B  xt_DrawMode(A2),D5 ; Test the draw mode 
        MOVE.B  D5,D6           ; Save a copy
        ANDI.W  #3,D5           ; Strip off INVERSVID (and any other) bit
        MOVE.W  D5,DRAWMODE(SP) ; and save the true drawmode
        CMP.B   #RP_JAM1,D5     ; Is it JAM1?
        BEQ     1$              ; If it's JAM1, leave the local back as zero
        MOVE.B  xt_BackPen(A2),D4 ; else load up the local back pen

1$      ANDI.B  #RP_INVERSVID,D6 ; Was INVERSVID bit set?
        BEQ     2$              ; and skip if not
        EXG     D3,D4           ; else exchange the pen registers

2$
        MOVE.W  D3,FRONTPEN(SP) ; Save the local front pen
        MOVE.W  D4,BACKPEN(SP)  ; Save the local back pen


        MOVE.W  xt_CharHeight(A2),D6    ; Build the bltsize word
        LSL.W   #6,D6           ; Height is shifted over as blitter likes it
        MOVE.W  D6,XBLTSIZE(SP) ; Save this partial for later


        CMP.W   D3,D4           ; Are the pens equal?
        BEQ     PLANESET        ; If so, skip building the planes


* Wait 'til any blitting is done, and then initialize 
* the blitter for my personal use ...
        CALLSYS WaitBlit        ; and wait for that baby to be free!


        CLEAR   D3
        MOVE.W  D3,bltamod(A5)  ; Set up the SRCA and SRCB modulos
        MOVE.W  D3,bltbmod(A5)
        SUBI.W  #1,D3
        MOVE.W  D3,bltafwm(A5)  ; Masks are all set
        MOVE.W  D3,bltalwm(A5)

        CLEAR   D3
        MOVE.B  xt_MaxTextWidth(A2),D3
        SUBQ.W  #2,D3
        MOVE.W  D3,bltdmod(A5)
        MOVE.W  #$0DFC,bltcon0(A5)      ; Use ABD, minterm is A or B, don't
        MOVE.W  #$8000,bltcon1(A5)      ; shift A, shift B by 8

        MOVE.L  xt_NormalTextPlane(A2),D3 ; Address of the normal text plane

        MOVE.W  xt_FontSelect(A2),D6    ; Get the address of the font data
        LSL.W   #2,D6
        LEA     xt_FontData(A2),A3
        ADD.W   D6,A3
        MOVE.L  (A3),A3
        MOVE.L  ARG1(SP),A4     ; Address of text string

        MOVE.W  xt_CharHeight(A2),D7

        MOVE.W  xt_Flags(A2),D6 ; Get the Flags
        ANDI    #SLIM_XTEXT,D6  ; and test if the programmer wants SLIM_XTEXT
        BNE     SLIM_CHARS      ; and go build the text the "slim" way if so
                                ; else we're doing text the faster "fat" way

        MOVE.W  XBLTSIZE(SP),D6 ; Get the blit size partial
        ORI     #1,D6           ; and make blit size one word wide

        CMPI    #8,D7           ; Can we do fast building?
        BNE     SLOW_PAIRLOOP   ; If not 8, do it the "slow" way


PAIRLOOP:
* The normal text plane is constructed here.
* Do as much pre-calculating as possible before actually waiting for the
* blitter to be ready for re-use.


        CLEAR   D4              ; Get the address of the font data of
        MOVE.B  (A4)+,D4        ; the next character
        LSL.W   #4,D4           ; (This presumes that each char is 16 bytes)
        ADD.L   A3,D4

        CLEAR   D5              ; Get font data of next in pair (if there's
        MOVE.B  (A4)+,D5        ; not really a second character (odd-numbered
        LSL.W   #4,D5           ; string lengths) then this second move of
        ADD.L   A3,D5           ; data will be unnecessary, but at least is
                                ; harmless since the buffer is *always*
                                ; pair-sized and the speed improvement is
                                ; great when handling two characters at once)

        CALLSYS WaitBlit        ; (this is redundant the first time through)

        MOVE.L  D4,bltapt(A5)
        MOVE.L  D5,bltbpt(A5)
        MOVE.L  D3,bltdpt(A5)

        MOVE.W  D6,bltsize(A5)  ; Bombs away!

        ADDQ.L  #2,D3           ; Advance destination pointer to next word

        DBRA    D2,PAIRLOOP

* Done, so go start setting up the planes 
        BRA     PLANESET


SLOW_PAIRLOOP:
* The normal text plane is constructed here.
* Done the "slow" way using a MULU because the character height isn't 8.
* Do as much pre-calculating as possible before actually waiting for the
* blitter to be ready for re-use.


        CLEAR   D4              ; Get the address of the font data of
        MOVE.B  (A4)+,D4        ; the next character
        ADD.W   D4,D4
        MULU    D7,D4           ; Offset * 2 * CharHeight
        ADD.L   A3,D4

        CLEAR   D5              ; Get font data of next in pair (if there's
        MOVE.B  (A4)+,D5        ; not really a second character (odd-numbered
        ADD.W   D5,D5           ; string lengths) then this second move of
        MULU    D7,D5
        ADD.L   A3,D5           ; data will be unnecessary, but at least is
                                ; harmless since the buffer is *always*
                                ; pair-sized and the speed improvement is
                                ; great when handling two characters at once)

        CALLSYS WaitBlit        ; (this is redundant the first time through)

        MOVE.L  D4,bltapt(A5)
        MOVE.L  D5,bltbpt(A5)
        MOVE.L  D3,bltdpt(A5)

        MOVE.W  D6,bltsize(A5)  ; Bombs away!

        ADDQ.L  #2,D3           ; Advance destination pointer to next word

        DBRA    D2,SLOW_PAIRLOOP

* Done, so go start setting up the planes 
        BRA     PLANESET



SLIM_CHARS:
* OK, the programmer asked for SLIM_XTEXT, which is the half-size memory 
* buffer for text to be built using the processor rather than the blitter

        MOVE.L  A5,-(SP)                ; Save A5 during the SLIM build

        CLEAR   D0                      ; Build the byte-size modulo
        MOVE.B  xt_MaxTextWidth(A2),D0
        SUBQ    #1,D0

        MOVE.W  D7,D1                   ; Build the line height (-1 for DBRA)
        SUBQ    #1,D1
        CMPI    #8,D7                   ; Can we do fast building?
        BNE     SLIM_SLOW_PAIRLOOP      ; If not 8, do it the "slow" way


SLIM_PAIRLOOP:
* The normal "slim" text plane is constructed here.

        CLEAR   D4              ; Get the address of the font data of
        MOVE.B  (A4)+,D4        ; the next character
        LSL.W   #3,D4           ; (This presumes that each char is 8 bytes)
        ADD.L   A3,D4
        MOVE.L  D4,A0

        CLEAR   D5              ; Get font data of next in pair (if there's
        MOVE.B  (A4)+,D5        ; not really a second character (odd-numbered
        LSL.W   #3,D5           ; string lengths) then this second move of
        ADD.L   A3,D5           ; data will be unnecessary, but at least is
        MOVE.L  D5,A1
                                ; harmless since the buffer is *always*
                                ; pair-sized and the speed improvement is
                                ; great when handling two characters at once)

        MOVE.W  D1,D6           ; Build the bltsize word
        MOVE.L  D3,A5

SLIM_BUILD:
        MOVE.B  (A0)+,(A5)+
        MOVE.B  (A1)+,(A5)
        ADD.L   D0,A5
        DBRA    D6,SLIM_BUILD

        ADDQ.L  #2,D3           ; Advance destination pointer to next word

        DBRA    D2,SLIM_PAIRLOOP ; Count down the character pairs to print 

        MOVE.L (SP)+,A5         ; Restore A5 at end of SLIM_BUILD 

* Done, so go start setting up the planes 
        BRA     PLANESET



SLIM_SLOW_PAIRLOOP:
* The normal "slim" text plane is slowly constructed here, slow because 
* the characters are other than 8 lines tall.

        CLEAR   D4              ; Get the address of the font data of
        MOVE.B  (A4)+,D4        ; the next character
        MULU    D7,D4
        ADD.L   A3,D4
        MOVE.L  D4,A0

        CLEAR   D5              ; Get font data of next in pair (if there's
        MOVE.B  (A4)+,D5        ; not really a second character (odd-numbered
        MULU    D7,D5           ; string lengths) then this second move
        ADD.L   A3,D5           ; will be unnecessary, but at least is
        MOVE.L  D5,A1
                                ; harmless since the buffer is *always*
                                ; pair-sized and the speed improvement is
                                ; great when handling two characters at once)

        MOVE.W  D1,D6           ; Build the bltsize word
        MOVE.L  D3,A5

SLIM_SLOW_BUILD:
        MOVE.B  (A0)+,(A5)+
        MOVE.B  (A1)+,(A5)
        ADD.L   D0,A5
        DBRA    D6,SLIM_SLOW_BUILD

        ADDQ.L  #2,D3           ; Advance destination pointer to next word

        DBRA    D2,SLIM_SLOW_PAIRLOOP ; Count down the pairs to print 

        MOVE.L (SP)+,A5         ; Restore A5 at end of SLIM_BUILD 

* Done, so fall into starting setting up the planes 



PLANESET:
* Well, that was quick, wasn't it.  Here all of the required planes (except
* the bothersome Inverted Text Plane) are ready to go.  So let's figure
* out how to initialize the plane pointers in the BitMap ...

        LEA     xt_TextBitMap(A2),A3
        LEA     bm_Planes(A3),A4        ; Get address of first plane pointer

        CLEAR   D0                      ; Set up the pen-test mask
        MOVE.W  FRONTPEN(SP),D1         ; Fetch the foreground pen
        MOVE.W  BACKPEN(SP),D2          ; and the background pen
        CLEAR   D3
        MOVE.B  bm_Depth(A3),D3         ; Get the BitMap depth ...
        SUBQ.W  #1,D3                   ; ... and set the loop count for DBRA
        CLEAR   D4                      ; Clear the Inverted flag


PLANELOOP:
* First, find out if the foreground/background pattern for this plane 
* is 00, 01, 10 or 11.  based on the pattern, select the associated 
* plane pointer for the BitMap
        BTST    D0,D1           ; If the bit is set in the foreground pen
        BNE     PAT_ONE         ; then go process 1x combinations

* Else we have a 0x combination.  Which one is it?
        BTST    D0,D2           ; This time test the background pen
        BNE     PAT_01          ; and if set then our pattern is 01

PAT_00:
        MOVE.L  xt_AllClearPlane(A2),A3 ; This plane is the 00 plane
        BRA     PLANE_STUFF

PAT_01:
* The dreaded Inverse Text Plane selector.  If it doesn't exist yet,
* I have to create it now.
        MOVE.L  xt_InverseTextPlane(A2),A3 ; This plane is the inverse plane
        TST.B   D4              ; Test our Inverted Plane flag
        BNE     PLANE_STUFF     ; and skip this mess if it's already set
                                ; else we'll have to invert it.
        ADDQ.B  #1,D4           ; Set the inversion flag

* OK, so use the blitter inversion algorithm:  Dest = NOT SRC.

        CLEAR   D5
        MOVE.B  xt_MaxTextWidth(A2),D5 ; Build the Group-of-2 values:
        MOVE.W  ARG2+2(SP),D6
        ADDQ.W  #1,D6           ; Get the first multiple of 2 that's greater
        ANDI.W  #$FFFE,D6       ; than or equal to the character count, and
        SUB.W   D6,D5           ; subtract that from the total buffer width
                                ; to make the Group-of-2 row modulo

        LSR.W   #1,D6           ; Turn char count into pair count
        OR.W    XBLTSIZE(SP),D6 ; and prepare for starting the blitter
 
        MOVEM.L D0-D1,-(SP)
        CALLSYS WaitBlit        ; Wait for the blitter before I jam it.
        MOVEM.L (SP)+,D0-D1

        MOVE.L  xt_NormalTextPlane(A2),bltbpt(A5)
        MOVE.L  A3,bltdpt(A5)

        MOVE.W  D5,bltbmod(A5)  ; Set up the SRCB and DEST modulos
        MOVE.W  D5,bltdmod(A5)

        MOVE.W  #$0533,bltcon0(A5)
        MOVE.W  #$0000,bltcon1(A5)

        MOVE.W  D6,bltsize(A5)  ; Bombs away!
                        
        BRA     PLANE_STUFF


PAT_ONE:
* We've got a 1x pattern.  Which one do you suppose it is?  Hmm ...
        BTST    D0,D2           ; This time, test the back pen only
        BNE     PAT_11


PAT_10:
* Normal Text Plane?  No problem.
        MOVE.L  xt_NormalTextPlane(A2),A3
        BRA     PLANE_STUFF

PAT_11:
        MOVE.L  xt_AllSetPlane(A2),A3   ; This plane must have all bits set

* and fall into ...


PLANE_STUFF:
* OK, so A3 has the address of this plane's data.  Stuff that baby
* into the BitMap structure.
        MOVE.L  A3,(A4)+

        ADDQ    #1,D0           ; Advance our mask to the next position

        DBRA    D3,PLANELOOP    ; Loop on the depth of the BitMap


* Now, all done with the private use of the blitter.  Here, I would prefer
* to retain exclusive use of the blitter even throughout the call to
* BlitBMRP below, but the system won't let me.  Too bad!


        CALLSYS DisownBlitter


* Well, now the BitMap is all ready to blast out our new line
* of text.  Wasn't that fun?  Now the simple final stroke:  zap that
* data into the RastPort, using the ever-popular, cleverly-named
* BltBitMapRastPort function (if Dale wasn't so good, I'd suggest
* that we take him out and shoot him for that name).
*
* BlitBMRP wants:
*
* A0 = Source BitMap  
* A1 = Destination RastPort
* A6 = GfxBase
*
* D0 = Source X
* D1 = Source Y
* D2 = Destination X
* D3 = Destination Y
* D4 = Pixel Width of block to be moved
* D5 = Scan-line Height of block to be moved
* D6 = Minterm

        CLEAR   D5
        MOVE.W  xt_CharHeight(A2),D5    ; Height of transfer

        MOVE.W  #XTEXT_CHARWIDTH,D4     ; Create the blit width ...
        MOVE.W  ARG2+2(SP),D3           ; Character count
        MULU    D3,D4                   ; Width of transfer (creates LONG)
 
        MOVE.L  ARG4(SP),D3             ; Get the Y position into D3
        MOVE.L  ARG3(SP),D2             ; Get the X position into D2

        CLEAR   D1                      ; Source x and y are 0
        CLEAR   D0

        MOVE.L  xt_OutputRPort(A2),A1   ; and get the RPort of the display

        LEA     xt_TextBitMap(A2),A0    ; Get the address of the BitMap arg

* The DrawMode defines what minterm we use and which routine we call.
        CMP.W   #RP_JAM1,DRAWMODE(SP)   ; Are we doing JAM1?
        BEQ     DO_JAM1

* The DrawMode can be JAM2 or COMPLEMENT.  Which is it, hmm?
        MOVE.L  #$C0,D6                 ; Minterm (simple transfer) for JAM2
        CMP.W   #RP_JAM2,DRAWMODE(SP)   ; Is it really JAM2?
        BEQ     BBMRP                   ; Branch if so

        MOVE.L  #$60,D6                 ; Only other is complement mode

BBMRP:
        CALLSYS BltBitMapRastPort       ; Do the normal (fast) BBMRP
        BRA     RETURN


DO_JAM1:
* Call BltMaskBitMapRastPort, which is the same as BBMRP except that a
* mask is used to cookie-cut the source into the destination.
* JAM1 requires a mask, because JAM1 is cookie-cut!
* The mask goes into A2.  If either pen was non-zero then we built 
* the normal plane so use that as the mask,
* else use the AllZeroPlane for the mask (which produces zip imagery!).
* The minterm sez:  cut me in, daddio.

        MOVE.B  xt_DrawMode(A2),D6      ; Was this inverse video?
        ANDI.B  #RP_INVERSVID,D6
        BNE     1$                      ; If so, use inverse JAM1 minterm
        MOVE.L  #$E0,D6                 ; else use normal JAM1 minterm
        BRA     2$

1$      MOVE.L  #$B0,D6

2$      TST.W   FRONTPEN(SP)            ; Was the front pen zero?
        BNE     3$                      ; If not then get Normal for mask
        TST.W   BACKPEN(SP)             ; Was the back pen zero?
        BNE     3$                      ; If not then get Normal for mask
        MOVE.L  xt_AllClearPlane(A2),A2 ; AllClearPlane is the mask
        BRA     4$

3$      MOVE.L  xt_NormalTextPlane(A2),A2 ; use the plane as the mask

4$      CALLSYS BltMaskBitMapRastPort   ; Do the not-as-fast BMBMRP



RETURN:
        MOVEM.L (SP)+,A2-A6/D2-D7
        LEA     8(SP),SP                ; Release memory of local variables

        RTS


        END


