/* @doc
 *
 * Functionality for 2D rendering
 *
 * Copyright (c) 1998 Criterion Software Ltd.
 */

/****************************************************************************
 *                                                                          *
 *  Module  :   ps2sgfnt.c                                                  *
 *                                                                          *
 *  Purpose :   graphics state                                              *
 *                                                                          *
 ****************************************************************************/

/****************************************************************************
 includes
 */

#include <math.h>

#include <rwcore.h>
#include <rpworld.h>

#include <rpdbgerr.h>

#include "rt2d.h"
#include "stroke.h"
#include "brush.h"
#include "font.h"
#include "gstate.h"

#include "ps2pipes.h"
#include "ps2sgfnt.h"

#include "vusgfnt.h"

/****************************************************************************
 local defines
 */

#define MESSAGE(_string)                                            \
    RwDebugSendMessage(rwDEBUGMESSAGE, "Rt2dPS2SingleFont", _string)

/****************************************************************************
 PS2 Functions
 */

/* Minium batch size for uploading.
 * 2 qw for string & brush context.
 * 4 qw for brush colour and uv.
 * 4 qw for CTM.
 */
#define RT2DPS2MINBATCHSIZE    10

#define     RT2DSGFONTPS2QWORD    1


static Rt2dPS2PipeState singleFontPipeState = {
    (RwUInt64) 0,               /* subdiv */
    (RxPipeline *)NULL,                       /* Pipeline */
    {NULL2D, NULL2D, NULL2D, NULL2D, /* vucode */
     NULL2D, NULL2D, NULL2D, NULL2D,
     NULL2D, NULL2D, NULL2D, NULL2D,
     NULL2D, NULL2D, NULL2D, NULL2D}
};

/*
 *
 */

static              RwInt32
PS2StrBatchGetSize2d(Rt2dPS2StrCache * strCache)
{
    RwInt32             sz;
    Rt2dPS2StrCache    *strCatchHead;

    RWFUNCTION(RWSTRING("PS2StrBatchGetSize2d"));

    sz = 0;

    strCatchHead = strCache;
    do
    {
        sz += ((strCache->numChar * 2) + RT2DPS2MINBATCHSIZE);

        strCache = strCache->nextCache;

    }
    while (strCache != strCatchHead);

    RWRETURN(sz);

}

static Rt2dPS2StrCache *
PS2FontAddStrCache2d(Rt2dFont * font, Rt2dPS2StrCache * strCache)
{
#if (0)
    RwBool              found;
#endif /* (0) */

    RWFUNCTION(RWSTRING("PS2FontAddStrCache2d"));

    /*
     * The batch list and cache list are circular linked. They always points
     * to the last element rather than the first. This allows quick access to
     * the first and last elements with just one ptr.
     */

    /* Add the str cache to the font. */
    if (font->strCache == NULL)
    {
        strCache->nextCache = strCache;
        font->strCache = (void *) strCache;
    }
    else
    {
        strCache->nextCache =
            ((Rt2dPS2StrCache *) font->strCache)->nextCache;

        ((Rt2dPS2StrCache *) font->strCache)->nextCache = strCache;

        font->strCache = (void *) strCache;
    }

    /* Add the font to batch list, if not there already. */
    if (font->nextBatch == NULL)
    {
        if (Rt2dGlobals.fontBatch == NULL)
        {
            font->nextBatch = font;
            Rt2dGlobals.fontBatch = font;
        }
        else
        {
            font->nextBatch = Rt2dGlobals.fontBatch->nextBatch;

            Rt2dGlobals.fontBatch->nextBatch = font;

            Rt2dGlobals.fontBatch = font;
        }
    }

    RWRETURN(strCache);
}

static Rt2dPS2StrCache *
PS2StrCacheGetFree2d(RwInt32 long_str)
{
    Rt2dPS2StrCache    *strCache;

    RWFUNCTION(RWSTRING("PS2StrCacheGetFree2d"));

    strCache = (Rt2dPS2StrCache *)NULL;

    /* First check if there sufficient room in the str buffer. */
    if (Rt2dGlobals.strBuffer.space >= long_str)
    {
        /* Check if there are any free str cache. */
        if (Rt2dGlobals.freeStrCache != NULL)
        {
            /* Adjust the free str cache. */
            strCache = Rt2dGlobals.freeStrCache;

            Rt2dGlobals.freeStrCache = strCache->nextCache;

            strCache->nextCache = (Rt2dPS2StrCache *)NULL;

            /* Adjust the str buffer. */
            strCache->strBuffer = Rt2dGlobals.strBuffer.str;

            Rt2dGlobals.strBuffer.str += long_str;
            Rt2dGlobals.strBuffer.space -= long_str;

            RWASSERT(Rt2dGlobals.strBuffer.space >= 0);
        }
    }

    RWRETURN(strCache);

}

RwBool
_rt2dPS2SingleFontFlush(void)
{
    Rt2dFont           *fontBatch;
    Rt2dFont           *fontBatchHead;
    Rt2dFont           *fontBatchNext;
    Rt2dPS2StrCache    *strCache;
    rwIm3DPool         *pool;
    rt2dCharRect       *cr;
#if (0)
    Rt2dFont           *fontBatchPrev;
#endif /* (0) */


    RWFUNCTION(RWSTRING("_rt2dPS2SingleFontFlush"));

    pool = _rwIm3DGetPool();

    fontBatchHead = Rt2dGlobals.fontBatch;

    if (fontBatchHead == NULL)
        RWRETURN(TRUE);

    fontBatchHead = fontBatchHead->nextBatch;
    fontBatch = fontBatchHead;

    do
    {
        Rt2dGlobals.strCache = (Rt2dPS2StrCache *)fontBatch->strCache;

        strCache = Rt2dGlobals.strCache;

        cr = &fontBatch->map[(RwUInt32) * strCache->str];
        RwRenderStateSet(rwRENDERSTATETEXTURERASTER, cr->bm);

        while (strCache != (Rt2dPS2StrCache *)NULL)
        {
            Rt2dGlobals.numVerts = PS2StrBatchGetSize2d(strCache) + 2;

            pool->elements = Rt2dGlobals.brush->vertex;
            pool->stride = 20; /* 5 x 4 */

            pool->stash.ltm = &strCache->ctm;
            pool->stash.flags = Rt2dGlobals.TransformFlags;

            pool->stash.pipeline = (RxPipeline *) NULL;
            pool->stash.primType = (RwPrimitiveType) rwPRIMTYPETRISTRIP;
            pool->stash.indices = (RxVertexIndex *) NULL;

            pool->numElements = (RwUInt16) Rt2dGlobals.numVerts;
            pool->stash.numIndices = (RwUInt16) Rt2dGlobals.numVerts;

            if (RxPipelineExecute
                (Rt2dGlobals.use_sgfont_pipe, (void *) &pool->stash,
                 FALSE) == NULL)
            {
                RWRETURN(FALSE);
            }

            /* Rt2dGlobals.strCache is updated during rendering. */
            strCache = Rt2dGlobals.strCache;
        }

        fontBatchNext = fontBatch->nextBatch;

        fontBatch->nextBatch = (Rt2dFont *)NULL;
        fontBatch->strCache = NULL;

        fontBatch = fontBatchNext;
    }
    while (fontBatch != fontBatchHead);

    Rt2dGlobals.fontBatch = (Rt2dFont *)NULL;
    Rt2dGlobals.strBuffer.str = Rt2dGlobals.strBuffer.buffer;
    Rt2dGlobals.strBuffer.space = RT2DSTRBUFFER;

    RWRETURN(TRUE);
}

static              RwBool
PS2SingleFontNode2d(RxPS2Mesh * ps2mesh,
                    RxPS2DMASessionRecord * __RWUNUSED__ dmaSessionRec,
                    RwReal * pVert)
{
    const RpMesh       *mesh;

    /*
     * const RpMeshHeader  *meshHeader;
     * rwPS2ResEntryHeader *ps2ResHeader;
     */

    RwInt32             qw, numVerts, batchSize, numChar;
    RwReal              baseu, *numCharPtr, *pPos;
    RwUInt64            tmp, tmp1;
    u_long128           ltmp = 0;
    Rt2dFont           *font;
    Rt2dPS2StrCache    *strCache, *strCacheHead, *strCacheLast,
        *strCacheNext;
    Rt2dFontChar       *str;
    rt2dCharRect       *cr;

    RWFUNCTION(RWSTRING("PS2SingleFontNode2d"));

    mesh = ps2mesh->mesh;
    numVerts = mesh->numIndices;

    pPos = pVert;

    batchSize = Rt2dGlobals.sgfont_batch_sz;

    strCacheLast = Rt2dGlobals.strCache;
    strCacheHead = strCacheLast->nextCache;
    strCache = strCacheHead;

    /* Reserve one qw for end marker. */
    qw = 0;
    numVerts--;

    do
    {
        /* Instance the DMA data */
        str = strCache->str;
        font = strCache->font;
        baseu = strCache->baseu;

        /* Upload the str context.
         * intergap, baseu, numChar
         */
        *pPos++ = strCache->font->intergap;
        *pPos++ = baseu;

        /* Remember this location. To be filled later. */
        numCharPtr = pPos;
        pPos += 2;

        /*
         * Upload the brush context.
         * halftwidth, inset, layerdepth, oobaseu
         */
        *pPos++ = strCache->halfwidth;
        *pPos++ = strCache->inset;
        *pPos++ = strCache->layerDepth;
        *pPos++ = strCache->oobaseu;

        /* Upload the brush colour. */
        *pPos++ = strCache->top.col.red * (RwReal) RT2DPS2COLSCALE;
        *pPos++ = strCache->top.col.green * (RwReal) RT2DPS2COLSCALE;
        *pPos++ = strCache->top.col.blue * (RwReal) RT2DPS2COLSCALE;
        *pPos++ = strCache->top.col.alpha * (RwReal) RT2DPS2COLSCALE;

        *pPos++ = strCache->dtop.col.red * (RwReal) RT2DPS2COLSCALE;
        *pPos++ = strCache->dtop.col.green * (RwReal) RT2DPS2COLSCALE;
        *pPos++ = strCache->dtop.col.blue * (RwReal) RT2DPS2COLSCALE;
        *pPos++ = strCache->dtop.col.alpha * (RwReal) RT2DPS2COLSCALE;

        *pPos++ = strCache->bottom.col.red * (RwReal) RT2DPS2COLSCALE;
        *pPos++ = strCache->bottom.col.green * (RwReal) RT2DPS2COLSCALE;
        *pPos++ = strCache->bottom.col.blue * (RwReal) RT2DPS2COLSCALE;
        *pPos++ = strCache->bottom.col.alpha * (RwReal) RT2DPS2COLSCALE;

        *pPos++ = strCache->dbottom.col.red * (RwReal) RT2DPS2COLSCALE;
        *pPos++ = strCache->dbottom.col.green * (RwReal) RT2DPS2COLSCALE;
        *pPos++ = strCache->dbottom.col.blue * (RwReal) RT2DPS2COLSCALE;
        *pPos++ = strCache->dbottom.col.alpha * (RwReal) RT2DPS2COLSCALE;

        /* Upload the CTM */
        *pPos++ = strCache->ctm.right.x;
        *pPos++ = strCache->ctm.right.y;
        *pPos++ = strCache->ctm.right.z;
        *pPos++ = strCache->ctm.right.z;

        *pPos++ = strCache->ctm.up.x;
        *pPos++ = strCache->ctm.up.y;
        *pPos++ = strCache->ctm.up.z;
        *pPos++ = strCache->ctm.up.z;

        *pPos++ = strCache->ctm.at.x;
        *pPos++ = strCache->ctm.at.y;
        *pPos++ = strCache->ctm.at.z;
        *pPos++ = strCache->ctm.at.z;

        *pPos++ = strCache->ctm.pos.x;
        *pPos++ = strCache->ctm.pos.y;
        *pPos++ = strCache->ctm.pos.z;
        *pPos++ = strCache->ctm.pos.z;

        /*
         * Update number of qword loaded.
         * 2 qw for string brush context.
         * 4 qw for brush colour and uv.
         * 4 qw for CTM.
         */
        qw += 10;
        batchSize -= 10;

        /* Upload the string. */
        numChar = 0;
        while ((strCache->numChar) &&
               (qw < (numVerts - 1)) && (batchSize > 4))
        {
            cr = &font->map[*str];

            *pPos++ = cr->uv[0].x;
            *pPos++ = cr->uv[0].y;
            *pPos++ = cr->width;
            pPos++;

            *pPos++ = cr->uv[1].x;
            *pPos++ = cr->uv[1].y;

            pPos += 2;

            baseu += (cr->width + font->intergap);
            str++;
            numChar++;

            /* 2 qw per vert. */
            qw += 2;
            batchSize -= 2;

            strCache->numChar--;
        }

        /* Now write in the num of chars uploaded. */
        *numCharPtr++ = *(RwReal *) & numChar;
        *numCharPtr = (RwReal) numChar;

        /* Check if we can upload the next batch. We need at least
         * 2 qw for string / brush cxt.
         * 4 qw for brush colour/uv
         * 4 qw for matrx.
         * 2 qw for one char.
         * 1 qw for end marker.
         * 2 qw for start of next str cache.
         * */
        if (batchSize < (RT2DPS2MINBATCHSIZE + 2 + 1 + 2))
        {
            /* Fill in a tag. */
            *pPos++ = (RwReal) 255.0;
            *pPos++ = (RwReal) 255.0;
            *pPos++ = (RwReal) 0.0;
            *pPos++ = (RwReal) 0.0;

            pPos += (4 * (batchSize - 3));
            qw += (batchSize - 2);

            batchSize = Rt2dGlobals.sgfont_batch_sz;
        }

        if (*str == '\0')
        {
            /*
             * We have uploaded the whole string, remove it from the rendering
             * queue.
             */
            RWASSERT(strCache->numChar == 0);

            /* Check if this is last cache in the list. */
            if (strCacheLast == strCache)
                Rt2dGlobals.strCache = (Rt2dPS2StrCache *)NULL;

            strCacheLast->nextCache = strCache->nextCache;

            strCacheNext = strCache->nextCache;

            /* Move the string cache to the short string free area. */
            strCache->strBuffer = (unsigned char *)NULL;

            strCache->nextCache = Rt2dGlobals.freeStrCache;
            Rt2dGlobals.freeStrCache = strCache;

            strCache = strCacheNext;

        }
        else
        {
            /*
             * We did not upload the whole string. Update some info for
             * next time.
             */
            strCache->baseu = baseu;
            strCache->str = str;
        }

    }
    /* Make sure were have at least enough qw left for next string */
    while ((strCache != strCacheHead) &&
           (qw < (numVerts - (RT2DPS2MINBATCHSIZE + 2))));

    /* Tag the last gword as cache end. */
    *pPos++ = (RwReal) 255.0;
    *pPos++ = (RwReal) 255.0;
    *pPos++ = (RwReal) 0.0;
    *pPos++ = (RwReal) 0.0;

    /*************************************************
        * Call a RW3-PS2-specific stealth function to
        * create enough space to store an additional 11
        * quadwords of DMA data.  In fact the RW code
        * will most likely already have enough spare
        * space, since it allocates memory in much bigger
        * chunks.
        * SWE_PKT_LOCAL: RW is responsible for the memory
        *    used for this new packet and will free it
        *    up when the packet has been sent.
        * SWE_PKT_CIRCALLOC: The memory used is taken
        *    from a circularly allocated buffer, which is
        *    much faster than calling regular memory
        *    functions.
        * SWE_PKT_VU1: The packet is destined for the
        *    Vector Unit 1.  (If the SWE_LPS_NOFIXUP
        *    symbol was not used here, then the DMA
        *    manager would try to combine further DMA
        *    packets if they were also destined for the
        *    VU1.)
        ************************************************/

    /* Reserve RT2DFONTPS2QWORD quadwords for the following data.
     * *
     * * Trilist giftag. We upload as tristrip to allow for
     * * any number of verts rather than just multiples of three.
     */
    sweFinaliseOpenLocalPkt(SWE_PKT_DMA_MODE_CHAIN_TTE |
                            SWE_PKT_LOCAL |
                            SWE_PKT_VU1 |
                            SWE_PKT_CIRCALLOC, -(RT2DSGFONTPS2QWORD + 2));

    /*************************************************
        * If the above call worked then we get a quad-
        * word pointer called sweLocalPacket which points
        * to the memory where we can fill in the DMA
        * data.  The macros below access this pointer and
        * advance this pointer.
        ************************************************/

    if (!sweLocalPacket)
    {
        printf("%s(%d): DMA memory failure\n", __FILE__, __LINE__);
    }
    else
    {
        RwUInt32            prim = 0x0l;

        /*************************************************
            * We build a DMA packet to transfer the necessary
            * data to the VIF.  This is a complete, ready-to
            * go packet, containing commands for the VIF. We
            * send data that must end up in the VU memory,
            * as well as commands that get passed to the GS
            * and program it's registers.
            * We end with an interrupt.
            ************************************************/

        /*************************************************
            * First off we set up a continuous DMA packet
            * to transfer 5 quadwords of data.
            ************************************************/

        tmp = (RT2DSGFONTPS2QWORD) | /* qwords */
            (1l << 28);        /* ID(cnt) */

        tmp1 =
        /*************************************************
            * We'll be transferring data with no scattering
            ************************************************/
            (VIFCMD_CYCLE | (4l << 8) | ((4))) |
        /*************************************************
            * Write 5 quadwords into VU1 memory
            ************************************************/
            /* UNPACK | qwords | where */
            ((VIFCMD_UNPACK | (RT2DSGFONTPS2QWORD << 16) |
              ((long) (vuSgFontSymbStaticData))) << 32);

        MAKE128(ltmp, tmp1, tmp);
        SWEADDCONTFAST(ltmp);

        /* giftag says use context 1 qw */
        prim = ( /* fix  */ 0x0l << 10 |
                /* ctxt */ 0x0l << 9 |
                /* fst  */ 0x0l << 8 |
                /* aa1  */ 0x0l << 7 |
                /* abe  */ 0x1l << 6 |
                /* fge  */ 0x0l << 5 |
                /* tme  */ 0x1l << 4 |
                /* iip  */ 0x1l << 3 |
                /* prim */ 0x3l << 0);

        tmp = ( /* regs */ 0x3l << (60 - 32) |
               /* flg  */ 0x0l << (58 - 32) |
               /* prim */ prim << (47 - 32) |
               /* pre  */ 0x1l << (46 - 32)) << 32;

        tmp1 = 0x412l;         /* registers */
        MAKE128(ltmp, tmp1, tmp);
        SWEADDCONTFAST(ltmp);

        /*    End with intr */
        tmp = (0xfl << 28);
        MAKE128(ltmp, 0l, tmp);
        SWEADDCONTFAST(ltmp);

        sweFinaliseOpenLocalPkt(SWE_LPS_CONT, 0);
    }

    /* Return the width of the string uploaded. */
    Rt2dGlobals.fontbaseu = baseu;

    RWRETURN(TRUE);
}

static              RwBool
Node2dPS2SingleFontPS2ManagerInstanceCallBack(void **clusterData,
                                              RwUInt32 __RWUNUSED__
                                              numClusters)
{
    RwBool              result;

    RWFUNCTION(RWSTRING("Node2dPS2SingleFontPS2ManagerInstanceCallBack"));

    result = PS2SingleFontNode2d((RxPS2Mesh *) (clusterData[0]),
                                 (RxPS2DMASessionRecord *) (clusterData[1]),
                                 (RwReal *) (clusterData[2]) /* Font Pos */
        );

    RWRETURN(result);
}

RxPipeline         *
_rt2dPS2SingleFontPipe(void)
{
    RxPipeline         *pipe;

    RWFUNCTION(RWSTRING("_rt2dPS2SingleFontPipe"));

    pipe = (RxPipeline *)NULL;

    /* Font Pipe. */
    pipe = RxPipelineCreate();

    if (pipe)
    {
        RwInt32             i;
        RxLockedPipe       *lpipe = (RxPipeline *)NULL;

        lpipe = RxPipelineLock(pipe);

        if (NULL != lpipe)
        {
            RxNodeDefinition   *ps2man;
            RxPipelineNode     *plnode, *result;

            // rwPS2MatInstancePvtData *pvtdata;

            ps2man = RxNodeDefinitionGetPS2Manager(rxOBJTYPE_IM3D);

            lpipe = RxLockedPipeAddFragment(lpipe,
                                            (RwUInt32 *)NULL,
                                            ps2man,
                                            (RxNodeDefinition *)NULL);
            RWASSERT(lpipe != NULL);

            plnode = RxPipelineFindNodeByName(lpipe,
                                              ps2man->name,
                                              (RxPipelineNode *)NULL,
                                              (RwInt32 *)NULL);
            RWASSERT(plnode != NULL);

            /* Set up the first to carry the char's u0, v0, width */
            Rt2dGlobals.RxClPS2SingleFont = RxClPS2user1;
            Rt2dGlobals.RxClPS2SingleFont.defaultAttributes &=
                ~CL_V4_32;
            Rt2dGlobals.RxClPS2SingleFont.defaultAttributes |= CL_V4_32;

            result =
                RxPipelineNodePS2ManagerGenerateCluster(plnode,
                                                        &Rt2dGlobals.
                                                        RxClPS2SingleFont,
                                                        CL_USER1);

            /* Set up the second to carry the char's u1, v1 */

            RxPipelineNodePS2ManagerSetVUBufferSizes(plnode, /* Node */
                                                     vuSgFontSymbStrideOfInputCluster, /* Stride of input cluster */
                                                     vuSgFontInputBufferSize, /* Tristrip vertex count */
                                                     vuSgFontSymbTLTriCount); /* Trilist vertex count */

            lpipe = RxLockedPipeUnlock(lpipe);
        }

        if (NULL != lpipe)
        {
            RxNodeDefinition   *ps2man;
            RxPipelineNode     *plnode, *result;

            // rwPS2MatInstancePvtData    *pvtData;

            ps2man = RxNodeDefinitionGetPS2Manager(rxOBJTYPE_IM3D);
            plnode = RxPipelineFindNodeByName(lpipe,
                                              ps2man->name,
                                              (RxPipelineNode *)NULL,
                                              (RwInt32 *)NULL);

            RWASSERT(plnode != NULL);

            /* We now insert our own instance callback */
            result =
                RxPipelineNodePS2ManagerSetInstanceCallBack(plnode,
                                                            Node2dPS2SingleFontPS2ManagerInstanceCallBack);

            /*  Set most of the transforms to be null */
            for (i = 0; i < 16; i++)
            {
                singleFontPipeState.vucode[i] = &singlefont2d;
            }

            /* Set the transforms/renderers */
            /* pipeState.vucode[TRANSNFOG | TRANSNCL | TRANSSTRIP | TRANSPER] = &font2d;  */

            /* We now insert our own VU code */
            result =
                RxPipelineNodePS2ManagerSetVU1CodeArray(plnode,
                                                        singleFontPipeState.vucode);

            RxPipelineNodePS2ManagerSetVIFOffset(plnode,
                                                 vuSgFontSymbVIFOffset);

            Rt2dGlobals.default_sgfont_pipe = pipe;
            Rt2dGlobals.use_sgfont_pipe = pipe;

            Rt2dGlobals.sgfont_batch_sz =
                RxPipelineNodePS2ManagerGetVUBatchSize(plnode,
                                                       rpMESHHEADERTRISTRIP);
        }
    }

    RWRETURN(pipe);
}

Rt2dFont           *
_rt2dPS2SingleFontShow(Rt2dFont * font,
                       const RwChar * string,
                       RwReal height, RwV2d * anchor, Rt2dBrush * brush)
{
    RwReal              oobaseu, layerDepth;
    RwInt32             length = 0;
    Rt2dPS2StrCache    *strCache;

    RWFUNCTION(RWSTRING("_rt2dPS2SingleFontShow"));

    layerDepth = Rt2dGlobals.layerDepth;

    if ((font) && (string))
    {
        RwMatrix           *ctm, *vtm;

        RWASSERT(font->flag & rtFONTFLAG_SINGLEPAGE);

        /* Get the string info. Short or long */
        _rt2dFontGetStringInfo(font,
                               (const RwChar *) string,
                               &oobaseu,
                               &length);

        if (length > 0)
        {

            /*
             * * Get a string cache for this string. If none are available.
             * * Flush the cache.
             */
            strCache = PS2StrCacheGetFree2d((length + 1));

            if (strCache == NULL)
            {
                _rt2dPS2SingleFontFlush();

                strCache = PS2StrCacheGetFree2d((length + 1));
            }

            /* establish geometry space */
            Rt2dCTMPush();
            Rt2dCTMTranslate(anchor->x, anchor->y);
            Rt2dCTMScale(height, height);
            Rt2dCTMTranslate(((RwReal) 0), -font->baseline);

            ctm = _rt2dCTMGet();
            vtm = RwCameraGetViewMatrix(Rt2dGlobals.viewCamera);

            RwMatrixMultiply(&strCache->ctm, ctm, vtm);

            strCache->font = font;
            strCache->top = brush->top;
            strCache->dtop = brush->dtop;
            strCache->bottom = brush->bottom;
            strCache->dbottom = brush->dbottom;
            strCache->halfwidth = brush->halfwidth;
            strCache->length = length;
            strCache->numChar = length;
            strCache->baseu = (RwReal) 0.0;
            strCache->oobaseu = oobaseu;
            strCache->inset = (RwReal) 0.0;
            strCache->layerDepth = Rt2dGlobals.layerDepth;

            /* Need to allocate the str buffer. */
            memcpy(strCache->strBuffer, string,
                   ((length + 1) * sizeof(Rt2dFontChar)));
            strCache->str = strCache->strBuffer;

            /* Add the strCache to the appropiate cache location.
             * * Sorted by font.
             */
            PS2FontAddStrCache2d(font, strCache);

            Rt2dCTMPop();

            /* write back final position */
            anchor->x += oobaseu * height;
        }
    }

    RWRETURN(font);
}

RwBool
_rt2dPS2SingleFontOpen(void)
{
    RwBool              result;
    RwInt32             i;

    RWFUNCTION(RWSTRING("_rt2dPS2SingleFontOpen"));

    result = TRUE;

    for (i = 0; i < (RT2DPS2STRCACHEMAX - 1); i++)
    {
        Rt2dGlobals.strCacheBuffer[i].nextCache =
            &Rt2dGlobals.strCacheBuffer[i + 1];

        Rt2dGlobals.strCacheBuffer[i].strBuffer = (unsigned char *)NULL;
        Rt2dGlobals.strCacheBuffer[i].str = (unsigned char *)NULL;
    }

    Rt2dGlobals.strCacheBuffer[i].nextCache = (Rt2dPS2StrCache *)NULL;

    Rt2dGlobals.strCacheBuffer[i].strBuffer = (unsigned char *)NULL;
    Rt2dGlobals.strCacheBuffer[i].str = (unsigned char *)NULL;

    Rt2dGlobals.freeStrCache = &Rt2dGlobals.strCacheBuffer[0];

    Rt2dGlobals.strCache = (Rt2dPS2StrCache *)NULL;
    Rt2dGlobals.fontBatch = (Rt2dFont *)NULL;

    Rt2dGlobals.strBuffer.str = Rt2dGlobals.strBuffer.buffer;
    Rt2dGlobals.strBuffer.space = RT2DSTRBUFFER;

    RWRETURN(result);
}

RwBool
_rt2dPS2SingleFontClose(void)
{
    RWFUNCTION(RWSTRING("_rt2dPS2SingleFontClose"));

    RWRETURN(TRUE);
}
