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

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

/****************************************************************************
 Includes
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <ctype.h>
#include <math.h>

#include <rwcore.h>
#include <rpdbgerr.h>

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

#if (defined(SKY2_DRVMODEL_H))
#include "ps2font.h"
#include "ps2sgfnt.h"
#endif /* (defined(SKY2_DRVMODEL_H)) */

static const char   rcsid[] __RWUNUSED__ =
    "@@(#)$Id: font.c,v 1.88 2001/09/26 12:42:15 johns Exp $";

/****************************************************************************
 Local Types
 */
typedef RwChar      RwStringFilename[128];
typedef RwChar      RwStringLine[256];

/****************************************************************************
 Local (Static) Prototypes
 */

/****************************************************************************
 Local Defines
 */

#define RwIsSpace(_c) (((RwChar)' ') == ((RwChar)(_c)))

/****************************************************************************
 Globals (across program)
 */

/****************************************************************************
 Local (static) Globals
 */

/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

   Functions

   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

/****************************************************************************/

/****************************************************************************/
static Rt2dFont    *
rt2dFontShowOutline(Rt2dFont * font,
                    const RwChar * string,
                    RwReal height, RwV2d * anchor, Rt2dBrush * brush)
{
    RwReal              layerDepth;
    const Rt2dFontChar *tmpstring;

    RWFUNCTION(RWSTRING("rt2dFontShowOutline"));

    layerDepth = Rt2dGlobals.layerDepth;

    if (font)
    {
        tmpstring = (const Rt2dFontChar *) string;

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

        while (*tmpstring)
        {
            rt2dCharRect       *cr;

            cr = &font->map[(RwUInt32) * tmpstring++];

            if (cr->bm == NULL)
            {
                Rt2dPathStroke(cr->path, brush);
                Rt2dCTMTranslate(cr->width + font->intergap,
                                 ((RwReal) 0));
            }
            else
            {
                /* use cached bitmap */
            }
        }

        Rt2dCTMPop();
    }

    RWRETURN(font);
}

static Rt2dFont    *
rt2dFontShowBitmap(Rt2dFont * font,
                   const RwChar * string,
                   RwReal height, RwV2d * anchor, Rt2dBrush * brush)
{
    RWIM3DVERTEX       *vdst;
    RwReal              x, y;
    RwReal              baseu;
    RwReal              oobaseu;
    RwReal              layerDepth;
    RwRaster           *currbm;
    RwInt32             vindex = 0;
    const Rt2dFontChar *tmpstring;

    RWFUNCTION(RWSTRING("rt2dFontShowBitmap"));

    layerDepth = Rt2dGlobals.layerDepth;

    if (font)
    {
        RwMatrix           *ctm;

        tmpstring = (const Rt2dFontChar *) string;

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

        ctm = _rt2dCTMGet();

        /* establish colour space */
        baseu = Rt2dFontGetStringWidth(font,
                                       (const RwChar *) tmpstring,
                                       ((RwReal) 1));
        oobaseu = ((RwReal) 1) / baseu;
        baseu = ((RwReal) 0);

        x = ((RwReal) 0);
        y = ((RwReal) 0);
        currbm = (RwRaster *)NULL;
        vdst = brush->vertex;
        vindex = 0;
        while (*tmpstring)
        {
            rt2dCharRect       *cr;
            rt2dShadeParameters sp;
            RwInt32             red;
            RwInt32             green;
            RwInt32             blue;
            RwInt32             alpha;

            cr = &font->map[(RwUInt32) * tmpstring++];

            /* do we need to flush? */
            if ((cr->bm != currbm) || (vindex > MAXVINDEX - 6))
            {
                /* anything to render? */
                if (vindex)
                {

                    if (RwIm3DTransform(brush->vertex,
                                        vdst - brush->vertex,
                                        ctm,
                                        rwIM3D_VERTEXUV |
                                        rwIM3D_NOCLIP))
                    {
                        RwIm3DRenderIndexedPrimitive
                            (rwPRIMTYPETRILIST,
                             Rt2dGlobals.fonttopo, vindex);
                        RwIm3DEnd();

#ifdef AMB_SPECIFICxx
#ifdef RWDEBUG
                        WireRender2d(brush->vertex,
                                     vdst - brush->vertex,
                                     fonttopo, vindex);
#endif
#endif
                    }
                }

                if (cr->bm != currbm)
                {
                    RwRenderStateSet(rwRENDERSTATETEXTURERASTER,
                                     cr->bm);
                    currbm = cr->bm;
                }

                vdst = brush->vertex;
                vindex = 0;

                /* we're going to overwrite the UVs */
                if (brush->texture && (brush->calcFields & FIELDUV))
                {
                    brush->calcFields &= ~FIELDUV;
                }
            }

            RWIM3DVERTEXSetPos(vdst, x, y, layerDepth);
            RWIM3DVERTEXSetU(vdst, cr->uv[0].x);
            RWIM3DVERTEXSetV(vdst, cr->uv[1].y);
            if (brush->calcFields & FIELDRGBA)
            {
                RwRGBARealScale(&sp.col, &brush->dbottom.col,
                                baseu * oobaseu);
                RwRGBARealAdd(&sp.col, &brush->bottom.col, &sp.col);

                red = (RwInt32) sp.col.red;
                green = (RwInt32) sp.col.green;
                blue = (RwInt32) sp.col.blue;
                alpha = (RwInt32) sp.col.alpha;

                RWIM3DVERTEXSetRGBA(vdst,
                                    (RwUInt8) red,
                                    (RwUInt8) green,
                                    (RwUInt8) blue, (RwUInt8) alpha);
            }
            vdst++;

            RWIM3DVERTEXSetPos(vdst, x, y + ((RwReal) 1), layerDepth);
            RWIM3DVERTEXSetU(vdst, cr->uv[0].x);
            RWIM3DVERTEXSetV(vdst, cr->uv[0].y);
            if (brush->calcFields & FIELDRGBA)
            {
                RwRGBARealScale(&sp.col, &brush->dtop.col,
                                baseu * oobaseu);
                RwRGBARealAdd(&sp.col, &brush->top.col, &sp.col);

                red = (RwInt32) sp.col.red;
                green = (RwInt32) sp.col.green;
                blue = (RwInt32) sp.col.blue;
                alpha = (RwInt32) sp.col.alpha;

                RWIM3DVERTEXSetRGBA(vdst,
                                    (RwUInt8) red,
                                    (RwUInt8) green,
                                    (RwUInt8) blue, (RwUInt8) alpha);
            }
            vdst++;

            RWIM3DVERTEXSetPos(vdst, x + cr->width, y, layerDepth);
            RWIM3DVERTEXSetU(vdst, cr->uv[1].x);
            RWIM3DVERTEXSetV(vdst, cr->uv[1].y);
            if (brush->calcFields & FIELDRGBA)
            {
                RwRGBARealScale(&sp.col, &brush->dbottom.col,
                                (baseu + cr->width) * oobaseu);
                RwRGBARealAdd(&sp.col, &brush->bottom.col, &sp.col);

                red = (RwInt32) sp.col.red;
                green = (RwInt32) sp.col.green;
                blue = (RwInt32) sp.col.blue;
                alpha = (RwInt32) sp.col.alpha;

                RWIM3DVERTEXSetRGBA(vdst,
                                    (RwUInt8) red,
                                    (RwUInt8) green,
                                    (RwUInt8) blue, (RwUInt8) alpha);
            }
            vdst++;

            RWIM3DVERTEXSetPos(vdst, x + cr->width,
                               y + ((RwReal) 1), layerDepth);
            RWIM3DVERTEXSetU(vdst, cr->uv[1].x);
            RWIM3DVERTEXSetV(vdst, cr->uv[0].y);
            if (brush->calcFields & FIELDRGBA)
            {
                RwRGBARealScale(&sp.col, &brush->dtop.col,
                                (baseu + cr->width) * oobaseu);
                RwRGBARealAdd(&sp.col, &brush->top.col, &sp.col);

                red = (RwInt32) sp.col.red;
                green = (RwInt32) sp.col.green;
                blue = (RwInt32) sp.col.blue;
                alpha = (RwInt32) sp.col.alpha;

                RWIM3DVERTEXSetRGBA(vdst,
                                    (RwUInt8) red,
                                    (RwUInt8) green,
                                    (RwUInt8) blue, (RwUInt8) alpha);
            }
            vdst++;

            vindex += 6;

            /* move to next character */
            x += cr->width + font->intergap;

            /* move to next shading param */
            baseu += cr->width + font->intergap;
        }

        /* render scragend */
        if (vindex)
        {

            if (RwIm3DTransform(brush->vertex,
                                vdst - brush->vertex, ctm,
                                rwIM3D_VERTEXUV | rwIM3D_NOCLIP))
            {
                RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST,
                                             Rt2dGlobals.fonttopo,
                                             vindex);
                RwIm3DEnd();

#ifdef AMB_SPECIFICxx
#ifdef RWDEBUG
                WireRender2d(brush->vertex,
                             vdst - brush->vertex, fonttopo, vindex);
#endif
#endif
            }
        }

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

        Rt2dCTMPop();
    }

    RWRETURN(font);
}

static Rt2dFont    *
FontReadMetrics2d1(Rt2dFont * const font,
                   RwFileFunctions * fileFuncs,
                   RwStringFilename * filename,
                   void *fp, RwStringLine * line)
{
    RwInt32             imgWidth, imgHeight, baseline;
    RwInt32             rasterWidth, rasterHeight;
    RwInt32             rasterDepth, rasterFlags;
    RwInt32             numtextures;
    RwChar              mask[80];

    RWFUNCTION(RWSTRING("FontReadMetrics2d1"));

    font->isOutline = FALSE;

    if (!(font->texdict = RwTexDictionaryCreate()))
    {
        Rt2dFontDestroy(font);

        RWRETURN((Rt2dFont *)NULL);
    }

    fileFuncs->rwfgets(&line[0][0], sizeof(*line), fp);

    numtextures =
        rwsscanf(&line[0][0], RWSTRING("%s %s\n"), filename, mask);

    if ((numtextures == 1) || (numtextures == 2))
    {
        RwImage            *img, *maskimg;
        RwReal              oowidth, ooheight;
        RwInt32             nChar, nLeft, nRight, nTop, nBottom;
        RwRaster           *raster;
        RwTexture          *texture;

        /* read bitmap info */
        RwImageSetPath(Rt2dGlobals.fontPath);

        if (!(img = RwImageRead(&filename[0][0])))
        {
            Rt2dFontDestroy(font);

            RWRETURN((Rt2dFont *)NULL);
        }

        /*
         * No mask specified. Check if there is 'm' prefix file and use it
         * as the mask. This is to support old method of mask loading.
         */
        if (numtextures == 1)
        {
            rwsprintf(mask, RWSTRING("m%s"), filename);
        }

        if ((maskimg = RwImageRead(mask)))
        {
            RwImageMakeMask(maskimg);
            RwImageApplyMask(img, maskimg);
            RwImageDestroy(maskimg);
        }

        imgWidth = RwImageGetWidth(img);
        imgHeight = RwImageGetHeight(img);

        /* Find the desired raster format */
        RwImageFindRasterFormat(img, rwRASTERTYPETEXTURE,
                                &rasterWidth, &rasterHeight,
                                &rasterDepth, &rasterFlags);

        /* Check if we got the exact raster size we wanted */
        if ((imgWidth != rasterWidth) || (imgHeight != rasterHeight))
        {
            if ((rasterWidth < imgWidth) || (rasterHeight < imgHeight))
            {
                RwInt32             maxDimension;
                RwInt32             msb;

                /* Try to allocate the smallest square, power of two raster */
                /* larger than or equal to the src image.                   */

                maxDimension =
                    (imgWidth > imgHeight) ? imgWidth : imgHeight;

                msb = RWLONGFINDMSB(maxDimension);

                if (maxDimension != (1 << msb))
                {
                    maxDimension = 1 << (msb + 1);
                }

                RwImageResize(img, maxDimension, maxDimension);

                RwImageFindRasterFormat(img, rwRASTERTYPETEXTURE,
                                        &rasterWidth, &rasterHeight,
                                        &rasterDepth, &rasterFlags);

                /* We still couldn't create a raster of the desired size so fail */
                if ((maxDimension != rasterWidth) ||
                    (maxDimension != rasterHeight))
                {
                    RwImageDestroy(img);
                    Rt2dFontDestroy(font);
                    RWRETURN((Rt2dFont *)NULL);
                }
            }
            else
            {
                /* The raster is bigger than the image so resize (not resample) */
                RwImageResize(img, rasterWidth, rasterHeight);
            }
        }

        raster = RwRasterCreate(rasterWidth,
                                rasterHeight,
                                rasterDepth,
                                rasterFlags |
                                ( rwRASTERFORMATAUTOMIPMAP |
                                  rwRASTERFORMATMIPMAP ) );

        RwRasterSetFromImage(raster, img);
        RwImageDestroy(img);

        /* Create a texture */
        texture = RwTextureCreate(raster);

        RwTextureSetName(texture, "a");

        RwTexDictionaryAddTexture(font->texdict, texture);

        /* leading info */
        fileFuncs->rwfgets(&line[0][0], sizeof(*line), fp);
        rwsscanf(&line[0][0], RWSTRING("%d"), &baseline);

        /* read character placement info */
        oowidth = ((RwReal) 1) / (RwReal) rasterWidth;
        ooheight = ((RwReal) 1) / (RwReal) rasterHeight;

        while (fileFuncs->rwfgets(&line[0][0], sizeof(*line), fp))
        {
            if (rwsscanf(&line[0][0], RWSTRING("%i %i %i %i %i\n"),
                         &nChar, &nLeft, &nTop, &nRight, &nBottom) == 5)
            {
                rt2dCharRect       *pChar;

                pChar = &font->map[(RwUInt32) nChar];

                pChar->uv[0].x = (RwReal) nLeft *oowidth;
                pChar->uv[0].y = (RwReal) nTop *ooheight;
                pChar->uv[1].x = (RwReal) nRight *oowidth;
                pChar->uv[1].y = (RwReal) nBottom *ooheight;

                pChar->bm = raster;
                pChar->width =
                    ((RwReal) (nRight - nLeft)) /
                    ((RwReal) (nBottom - nTop));

                font->charpage[(RwUInt32) nChar] = 'a';
            }
        }

        /* font info */
        font->height = (RwReal) (nBottom - nTop);
        font->baseline = (RwReal) baseline / font->height;
        font->intergap = ((RwReal) 0);
        font->flag |= rtFONTFLAG_SINGLEPAGE;

#ifdef SKY2_DRVMODEL_H

        font->fontshowCB = _rt2dPS2SingleFontShow;

#endif /* SKY2_DRVMODEL_H */

    }

    RWRETURN(font);
}

static Rt2dFont    *
FontReadMetrics2d2(Rt2dFont * const font,
                   RwFileFunctions * fileFuncs,
                   RwStringFilename * filename,
                   void *fp, RwStringLine * line)
{

    RwInt32             imgWidth, imgHeight, baseline;
    RwChar              mask[80];
    RwChar              texname[] = "a";
    RwUInt32            pagenum = 0;
    RwUInt32            numtextures;

    RWFUNCTION(RWSTRING("FontReadMetrics2d2"));

    font->isOutline = FALSE;

    if (!(font->texdict = RwTexDictionaryCreate()))
    {
        Rt2dFontDestroy(font);

        RWRETURN((Rt2dFont *)NULL);
    }

    while ((pagenum < rtFONTMAXPAGES) &&
           fileFuncs->rwfgets(&line[0][0], sizeof(*line), fp) &&
           (numtextures = rwsscanf(&line[0][0],
                                   RWSTRING("%s %s\n"), filename,
                                   mask)))
    {
        RwImage            *img, *maskimg = (RwImage *)NULL;
        RwReal              oowidth, ooheight;
        RwInt32             depth, x, y, nHeight;
        RwInt32             nChar, nLeft, nRight, nTop, nBottom;
        RwUInt8            *pix, *testpix;
        RwChar              character;
        RwRaster           *raster;
        RwTexture          *texture;

        if ((numtextures != 1) && (numtextures != 2))
        {
            break;
        }

        /* read bitmap info */
        RwImageSetPath(Rt2dGlobals.fontPath);

        if (!(img = RwImageRead(&filename[0][0])))
        {
            Rt2dFontDestroy(font);

            RWRETURN((Rt2dFont *)NULL);
        }

        imgWidth = RwImageGetWidth(img);
        imgHeight = RwImageGetHeight(img);
        oowidth = ((RwReal) 1) / (RwReal) imgWidth;
        ooheight = ((RwReal) 1) / (RwReal) imgHeight;

        raster = RwRasterCreate(imgWidth, imgHeight,
                                0, rwRASTERTYPETEXTURE);

        /* leading info */
        fileFuncs->rwfgets(&line[0][0], sizeof(*line), fp);
        rwsscanf(&line[0][0], "%d", &baseline);

        /* extract character placements */
        pix = RwImageGetPixels(img);
        depth = RwImageGetDepth(img) >> 3;
        nHeight = 0;

        for (y = 1; y < imgHeight; y++)
        {
            testpix = pix + RwImageGetStride(img) * y;

            if (memcmp(pix, testpix, depth) == 0)
            {
                nHeight = y;
                break;
            }
        }

        /* Make sure we have a valid height */
        RWASSERT(nHeight > 0);

        for (y = 0; y < imgHeight; y += nHeight)
        {
            nLeft = 0;
            for (x = 1; x < imgWidth; x++)
            {
                testpix = pix + RwImageGetStride(img) * y + depth * x;
                if (memcmp(pix, testpix, depth) == 0)
                {
                    /* zero width marks end of line */
                    if (x - nLeft == 1)
                    {
                        break;
                    }
                    else
                    {

                        rt2dCharRect       *pChar;

                        nRight = x;
                        nTop = y;
                        nBottom = nTop + nHeight;
                        fileFuncs->rwfgets(&line[0][0],
                                           2 * sizeof(RwChar), fp);
                        rwsscanf(&line[0][0], RWSTRING("%c"),
                                 &character);
                        nChar = (unsigned char) character;

                        pChar = &font->map[(RwUInt32) nChar];

                        pChar->uv[0].x = (RwReal) (nLeft + 2) * oowidth;
                        pChar->uv[0].y = (RwReal) (nTop + 2) * ooheight;
                        pChar->uv[1].x =
                            (RwReal) (nRight - 2) * oowidth;
                        pChar->uv[1].y =
                            (RwReal) (nBottom - 2) * ooheight;
                        pChar->bm = raster;
                        pChar->width =
                            ((RwReal) (nRight - nLeft - 4)) /
                            ((RwReal) (nBottom - nTop - 2));

                        font->charpage[(RwUInt32) nChar] =
                            'a' + (RwChar) pagenum;

                        nLeft = nRight;
                    }
                }
            }
        }

        /* Get the carriage return */
        fileFuncs->rwfgets(&line[0][0], 2 * sizeof(RwChar), fp);

        if (numtextures == 2)
        {
            RwInt32             rasterWidth, rasterHeight, rasterDepth,
                rasterFlags;
            RwRaster           *oldraster;
            RwReal              uscale, vscale;

            oldraster = raster;
            RwRasterDestroy(raster);

            maskimg = RwImageRead(mask);
            RwImageMakeMask(maskimg);
            RwImageApplyMask(img, maskimg);
            RwImageDestroy(maskimg);

            /* Find the desired raster format */
            RwImageFindRasterFormat(img, rwRASTERTYPETEXTURE,
                                    &rasterWidth, &rasterHeight,
                                    &rasterDepth, &rasterFlags);

            /* Check if we got the exact raster size we wanted */
            if ((imgWidth != rasterWidth) ||
                (imgHeight != rasterHeight))
            {
                if ((rasterWidth < imgWidth) ||
                    (rasterHeight < imgHeight))
                {
                    RwInt32             maxDimension;
                    RwInt32             msb;

                    /* Try to allocate the smallest square, power of two raster */
                    /* larger than or equal to the src image.                   */

                    maxDimension =
                        (imgWidth > imgHeight) ? imgWidth : imgHeight;

                    msb = RWLONGFINDMSB(maxDimension);

                    if (maxDimension != (1 << msb))
                    {
                        maxDimension = 1 << (msb + 1);
                    }

                    RwImageResize(img, maxDimension, maxDimension);

                    RwImageFindRasterFormat(img, rwRASTERTYPETEXTURE,
                                            &rasterWidth, &rasterHeight,
                                            &rasterDepth, &rasterFlags);

                    /* We still couldn't create a raster of the desired size so fail */
                    if ((maxDimension != rasterWidth) ||
                        (maxDimension != rasterHeight))
                    {
                        RwImageDestroy(img);
                        Rt2dFontDestroy(font);
                        RWRETURN((Rt2dFont *)NULL);
                    }
                }
                else
                {
                    /* The raster is bigger than the image so resize (not resample) */
                    RwImageResize(img, rasterWidth, rasterHeight);
                }
            }

            raster = RwRasterCreate(rasterWidth,
                                    rasterHeight,
                                    rasterDepth,
                                    rasterFlags |
                                    ( rwRASTERFORMATAUTOMIPMAP |
                                      rwRASTERFORMATMIPMAP ) );

            /* Rescale the UVs if we resized the image */
            uscale = (RwReal) imgWidth / (RwReal) rasterWidth;
            vscale = (RwReal) imgHeight / (RwReal) rasterHeight;

            for (x = 0; x < rtFONTCHARCOUNT; x++)
            {
                if (font->map[(RwUInt32) x].bm == oldraster)
                {
                    font->map[(RwUInt32) x].bm = raster;
                    font->map[(RwUInt32) x].uv[0].x *= uscale;
                    font->map[(RwUInt32) x].uv[0].y *= vscale;
                    font->map[(RwUInt32) x].uv[1].x *= uscale;
                    font->map[(RwUInt32) x].uv[1].y *= vscale;
                }
            }
        }

        RwRasterSetFromImage(raster, img);
        RwImageDestroy(img);

        /* Create a texture */
        texture = RwTextureCreate(raster);

        texname[0] = 'a' + (RwChar) pagenum;

        RwTextureSetName(texture, texname);

        RwTexDictionaryAddTexture(font->texdict, texture);

        /* font info */
        font->height = (RwReal) nHeight - 2;
        font->baseline = (RwReal) baseline / font->height;
        font->intergap = ((RwReal) 0);

        /* onto next page */
        pagenum++;
    }

    /*
     * If there is only one page, we don't need to check for textures during rendering.
     * This eliminates one loop.
     */
    if (pagenum == 1)
    {
        font->flag |= rtFONTFLAG_SINGLEPAGE;

#ifdef SKY2_DRVMODEL_H

        font->fontshowCB = _rt2dPS2SingleFontShow;

#endif /* SKY2_DRVMODEL_H */
    }
    else
    {
#ifdef SKY2_DRVMODEL_H

        font->fontshowCB = _rt2dPS2FontShow;

#endif /* SKY2_DRVMODEL_H */

    }

    RWRETURN(font);
}

static Rt2dFont    *
FontReadMetrics2d3(Rt2dFont * const font,
                   RwFileFunctions * fileFuncs,
                   RwStringFilename * filename __RWUNUSED__,
                   void *fp, RwStringLine * line)
{

    Rt2dPath           *path;
    RwChar              facename[128];

    RWFUNCTION(RWSTRING("FontReadMetrics2d3"));

    font->isOutline = TRUE;
    font->height = ((RwReal) 1);
    font->baseline = ((RwReal) 0);
    font->intergap = ((RwReal) 0);

    path = (Rt2dPath *)NULL;
    fileFuncs->rwfgets(&line[0][0], sizeof(*line), fp);

    if (rwsscanf(&line[0][0], RWSTRING("%[^\n]"), facename) == 1)
    {
        RwChar              nChar, glyph;

        nChar = 0;

        while (fileFuncs->rwfgets(&line[0][0], sizeof(*line), fp))
        {
            RwReal              x[3], y[3];

            if (nChar == 0)
            {
                if (rwsscanf(&line[0][0], RWSTRING("'%c'\n"), &glyph) ==
                    1)
                {
                    nChar = glyph;
                }
            }
            else
            {
                if (!rwstrcmp(&line[0][0], RWSTRING("begin\n")))
                {
                    path = Rt2dPathCreate();
                }
                else if (!rwstrcmp(&line[0][0], RWSTRING("end\n")))
                {
                    rt2dCharRect       *pChar;

                    path = _rt2dPathOptimize(path);

                    pChar = &font->map[(RwUInt32) nChar];

                    pChar->path = path;
                    pChar->width = x[0];
                    nChar = 0;
                    path = (Rt2dPath *)NULL;
                }
                else if (path)
                {
                    /* don't add until we have a path */
                    if (rwsscanf(&line[0][0],
                                 RWSTRING("moveto %f %f\n"),
                                 &x[0], &y[0]) == 2)
                    {
                        Rt2dPathMoveto(path, x[0], y[0]);
                    }
                    else if (rwsscanf(&line[0][0],
                                      RWSTRING("lineto %f %f\n"),
                                      &x[0], &y[0]) == 2)
                    {
                        Rt2dPathLineto(path, x[0], y[0]);
                    }
                    else if (rwsscanf(&line[0][0],
                                      RWSTRING
                                      ("curveto %f %f %f %f %f %f\n"),
                                      &x[0], &y[0], &x[1], &y[1], &x[2],
                                      &y[2]) == 6)
                    {
                        Rt2dPathCurveto(path,
                                        x[0], y[0], x[1], y[1], x[2],
                                        y[2]);
                    }
                    else if (!rwstrcmp
                             (&line[0][0], RWSTRING("closepath\n")))
                    {
                        Rt2dPathClose(path);
                    }

                    Rt2dPathFlatten(path);
                }
            }
        }
    }

    font->fontshowCB = rt2dFontShowOutline;

    RWRETURN(font);
}

/****************************************************************************/

static Rt2dFont    *
FontCreate2d(void)
{
    Rt2dFont           *font;
    RwInt32             i;

    RWFUNCTION(RWSTRING("FontCreate2d"));

    font = (Rt2dFont *)RwFreeListAlloc(Rt2dGlobals.fontFreeList);
    if (!font)
    {
        RWRETURN((Rt2dFont *)NULL);
    }

    /* initialise */
    font->isOutline = TRUE;
    font->height = ((RwReal) 1);
    font->baseline = ((RwReal) 0);
    font->intergap = ((RwReal) 0);
    font->flag = ((RwUInt32) 0);

    font->texdict = (RwTexDictionary *)NULL;

    for (i = 0; i < rtFONTCHARCOUNT; i++)
    {
        font->map[i].width = ((RwReal) 0);
        font->map[i].bm = (RwRaster *)NULL;
        font->map[i].uv[0].x = ((RwReal) 0);
        font->map[i].uv[0].y = ((RwReal) 0);
        font->map[i].uv[1].x = ((RwReal) 0);
        font->map[i].uv[1].y = ((RwReal) 0);

        font->map[i].path = (Rt2dPath *)NULL;

        font->charpage[i] = 0;

        font->fontshowCB = rt2dFontShowBitmap;

        font->nextBatch = (Rt2dFont *)NULL;
        font->strCache = NULL;
    }

    RWRETURN(font);
}

/***************************************************************************
 */
RwUInt32
_rt2dFontStreamGetSize(Rt2dFont * font)
{
    RwUInt32            size;

    RWFUNCTION(RWSTRING("_rt2dFontStreamGetSize"));

    if (!font)
    {
        RWRETURN(0);
    }

    if (font->isOutline)
    {
        /* can't handle outline fonts yet */
        RWRETURN(0);
    }

    size = sizeof(Rt2dFont) + rwCHUNKHEADERSIZE;

    if (!font->isOutline)
    {
        if (font->texdict)
        {
            size +=
                RwTexDictionaryStreamGetSize(font->texdict) +
                rwCHUNKHEADERSIZE;
        }
    }

    RWRETURN(size);
}

/***************************************************************************
 */
Rt2dFont           *
_rt2dFontStreamWrite(Rt2dFont * font, RwStream * stream)
{
    RwUInt32            size;

    RWFUNCTION(RWSTRING("_rt2dFontStreamWrite"));

    if (!font || !stream)
    {
        RWRETURN((Rt2dFont *)NULL);
    }

    if (font->isOutline)
    {
        /* can't handle outline fonts yet */
        RWRETURN((Rt2dFont *)NULL);
    }

    size = _rt2dFontStreamGetSize(font);

    /* write out the font header */
    if (!RwStreamWriteChunkHeader(stream, rwID_2DFONT, size))
    {
        RWRETURN((Rt2dFont *)NULL);
    }

    if (!RwStreamWriteChunkHeader
        (stream, rwID_STRUCT, sizeof(Rt2dFont)))
    {
        RWRETURN((Rt2dFont *)NULL);
    }

    if (!RwStreamWrite(stream, font, sizeof(Rt2dFont)))
    {
        RWRETURN((Rt2dFont *)NULL);
    }

    if (!font->isOutline)
    {
        if (font->texdict)
        {
            if (!RwTexDictionaryStreamWrite(font->texdict, stream))
            {
                RWRETURN((Rt2dFont *)NULL);
            }
        }
    }

    RWRETURN(font);
}

/***************************************************************************
 */
Rt2dFont           *
_rt2dFontStreamRead(RwStream * stream)
{
    Rt2dFont           *font;

    RwUInt32            size, version;

    RWFUNCTION(RWSTRING("_rt2dFontStreamRead"));

    if (!RwStreamFindChunk(stream, rwID_STRUCT, &size, &version))
    {
        RWRETURN((Rt2dFont *)NULL);
    }

    if (size != sizeof(Rt2dFont))
    {
        RWRETURN((Rt2dFont *)NULL);
    }

    font = FontCreate2d();
    if (!font)
    {
        RWRETURN((Rt2dFont *)NULL);
    }

    if (RwStreamRead(stream, font, size) != size)
    {
        Rt2dFontDestroy(font);

        RWRETURN((Rt2dFont *)NULL);
    }

    if (!font->isOutline)
    {
        RwUInt32            nChar;

        RwChar              texname[] = "a";
        RwTexture          *texture;

        /* now load in the texture dictionary */
        if (!RwStreamFindChunk
            (stream, rwID_TEXDICTIONARY, &size, &version))
        {
            Rt2dFontDestroy(font);

            RWRETURN((Rt2dFont *)NULL);
        }

        if (!(font->texdict = RwTexDictionaryStreamRead(stream)))
        {
            Rt2dFontDestroy(font);

            RWRETURN((Rt2dFont *)NULL);
        }

        for (nChar = 0; nChar < rtFONTCHARCOUNT; nChar++)
        {
            if (font->map[nChar].bm)
            {
                texname[0] = font->charpage[nChar];

                if (!(texture =
                      RwTexDictionaryFindNamedTexture(font->texdict,
                                                      texname)))
                {
                    Rt2dFontDestroy(font);

                    RWRETURN((Rt2dFont *)NULL);
                }

                if (!
                    (font->map[nChar].bm = RwTextureGetRaster(texture)))
                {
                    Rt2dFontDestroy(font);

                    RWRETURN((Rt2dFont *)NULL);
                }
            }
        }

        /*  The values loaded into these pointers will no longer be valid.
         *  Change them to default values to avoid crashes
         */
        font->fontshowCB = rt2dFontShowBitmap;
        font->nextBatch = (Rt2dFont *)NULL;
        font->strCache = NULL;
    }

    RWRETURN(font);
}

/****************************************************************************/

#ifdef AMB_SPECIFICxx
#ifdef RWDEBUG
static void
WireRender2d(RWIM3DVERTEX * vertex, int vcount,
             RWIMVERTEXINDEX * topo, int icount)
{
    RWIM3DVERTEX        local[256];
    int                 i;

    RWFUNCTION(RWSTRING("WireRender2d"));

    for (i = 0; i < vcount; i++)
    {
        local[i] = vertex[i];
        RWIM3DVERTEXSetRGBA(&(local[i]), 255, 255, 0, 255);
    }

    if (RwIm3DTransform(local, vcount, _rt2dCTMGet(), 0))
    {
        RwRenderStateSet(rwRENDERSTATETEXTURERASTER, NULL);
        RwIm3DRenderIndexedPrimitive(rwPRIMTYPEPOLYLINE, topo, icount);
        RwIm3DEnd();
    }

    RWRETURNVOID();
}
#endif /* RWDEBUG */
#endif /* AMB_SPECIFICxx */

/****************************************************************************/
void
_rt2dFontClose(void)
{
    RWFUNCTION(RWSTRING("_rt2dFontClose"));

    RwFreeListDestroy(Rt2dGlobals.fontFreeList);

    Rt2dGlobals.fontFreeList = (RwFreeList *)NULL;

    RWRETURNVOID();
}

/****************************************************************************/
RwBool
_rt2dFontOpen(void)
{
    RwInt32             i;
    RWIMVERTEXINDEX     j, *fonttopo;

    RWFUNCTION(RWSTRING("_rt2dFontOpen"));

    Rt2dGlobals.fontFreeList =
        RwFreeListCreate(sizeof(Rt2dFont), 10, 0);

    Rt2dFontSetPath(RWSTRING("fonts\\"));

    fonttopo = Rt2dGlobals.fonttopo;

    for (i = 0, j = 0; i < MAXVINDEX; i += 6, j += 4)
    {
        fonttopo[i + 0] = j;
        fonttopo[i + 1] = j + 2;
        fonttopo[i + 2] = j + 3;
        fonttopo[i + 3] = j;
        fonttopo[i + 4] = j + 3;
        fonttopo[i + 5] = j + 1;
    }

    RWRETURN(TRUE);
}

/****************************************************************************/

/**
 * \ingroup rt2d
 * \ref Rt2dFontSetPath
 * is used to specify the current search path for reading
 * fonts from the file system.
 *
 * The search path can be considered to be either absolute or
 * relative. In the latter case the search path is relative to the
 * directory from which the application executable is running (the
 * current directory).
 *
 * When fonts are read the image path is changed.
 *
 * Always include a trailing path separator in the directory name when
 * setting the search path.
 *
 * The include file rt2d.h and the library file rt2d.lib are required to
 * use this function.
 * \param path  Pointer to a string containing the current font search path.
 * \return pointer to the font path if successful or NULL if there is an
 * error.
 * \see Rt2dFontRead
 * \see RwImageSetPath
 */
const RwChar       *
Rt2dFontSetPath(const RwChar * path)
{
    RWAPIFUNCTION(RWSTRING("Rt2dFontSetPath"));

    if (path)
    {
        rwstrcpy(Rt2dGlobals.fontPath, path);

        RWRETURN(path);
    }

    RWRETURN((const char *)NULL);
}

/****************************************************************************/

/**
 * \ingroup rt2d
 * \ref Rt2dFontGetHeight
 * is used to retrieve the 'natural' height of the
 * specified font. For outline fonts this function always returns 1.0,
 * but for bitmap fonts the returned value will depend on the current
 * view settings and the CTM. Therefore, using the bitmap font height
 * when rendering text ensures there is a one-to-one mapping to the
 * display; hence the text's rendered size remains independent of current
 * transformations.
 *
 * The include file rt2d.h and the library file rt2d.lib are required to
 * use this function.
 * \param font  Pointer to the font.
 * \return a RwReal value equal to the height of the font if successful
 * or zero if there is an error.
 * \see Rt2dFontGetStringWidth
 * \see Rt2dFontSetIntergapSpacing
 */
RwReal
Rt2dFontGetHeight(Rt2dFont * font)
{
    RwReal              result = ((RwReal) 0);
    RwV2d               xstep, ystep, origin;

    RWAPIFUNCTION(RWSTRING("Rt2dFontGetHeight"));

    RWASSERT(font);

    Rt2dDeviceGetStep(&xstep, &ystep, &origin);

    /*
     * result = (font->height *
     *           (RwReal)_rwSqrt(ystep.x * ystep.x +
     *                           ystep.y * ystep.y));
     */

    result = font->height * RwV2dLength(&ystep);

    RWRETURN(result);
}

Rt2dFont           *
_rt2dFontGetStringInfo(Rt2dFont * font, const RwChar * string,
                       RwReal * width, RwInt32 * l)
{
    RwReal              w;
    RwInt32             c;

    const Rt2dFontChar *tmpstring;

    RWFUNCTION(RWSTRING("_rt2dFontGetStringInfo"));

    w = ((RwReal) 0);
    c = 0;

    RWASSERT(font);

    /* colour space */
    tmpstring = (const Rt2dFontChar *) string;

    while (*tmpstring)
    {
        rt2dCharRect       *cr = &font->map[(RwUInt32) * tmpstring++];

        w += (cr->width + font->intergap);
        c++;
    }

    *width = w;
    *l = c;

    RWRETURN(font);
}

/****************************************************************************/

/**
 * \ingroup rt2d
 * \ref Rt2dFontGetStringWidth
 * is used to determine the width of the specified
 * string if it were to be rendered at the given height using the given
 * font.
 *
 * The include file rt2d.h and the library file rt2d.lib are required to
 * use this function.
 * \param font  Pointer to the font.
 * \param string  Pointer to the string.
 * \param height  A RwReal value equal to the height.
 * \return a RwReal value equal to the string's width if successful or
 * zero if there is an error.
 * \see Rt2dFontGetHeight
 * \see Rt2dFontSetIntergapSpacing
 * \see Rt2dFontShow
 */
RwReal
Rt2dFontGetStringWidth(Rt2dFont * font, const RwChar * string,
                       RwReal height)
{
    RwReal              width;
    const Rt2dFontChar *tmpstring;

    RWAPIFUNCTION(RWSTRING("Rt2dFontGetStringWidth"));

    width = ((RwReal) 0);
    RWASSERT(font);

    /* colour space */
    tmpstring = (const Rt2dFontChar *) string;

    while (*tmpstring)
    {
        rt2dCharRect       *cr = &font->map[(RwUInt32) * tmpstring++];

        width += (cr->width + font->intergap);
    }

    RWRETURN(width * height);
}

/****************************************************************************/

/****************************************************************************/

/**
 * \ingroup rt2d
 * \ref Rt2dFontFlow
 * is used to render the specified string in the given
 * bounding-box and have the text wrap over several lines if
 * necessary. The string is rendered using the specified font, at the
 * specified height and with the given brush. The text is justified
 * within the boundaries of the box according to the specified format.
 *
 * Note that text is rendered until it reaches the end of the string or
 * no more text can be accommodated inside the bounding-box, whichever
 * comes first.
 *
 * The include file rt2d.h and the library file rt2d.lib are required to
 * use this function.
 * \param font  Pointer to the font.
 * \param string  Pointer to a string containing the text to show.
 * \param height  A RwReal value equal to the height of the rendered text.
 * \param bbox  Pointer to the bounding-box.
 * \param format  Text justification.
 * \param brush  Pointer to the brush.
 * \return a pointer to the font if successful or NULL if there is an
 * error.
 * \see Rt2dFontShow
 * \see Rt2dFontRead
 */
Rt2dFont           *
Rt2dFontFlow(Rt2dFont * font,
             RwChar * string,
             RwReal height,
             Rt2dBBox * bbox, Rt2dJustificationType format,
             Rt2dBrush * brush)
{
    RwReal              bwidth;
    RwReal              justscale;

    RWAPIFUNCTION(RWSTRING("Rt2dFontFlow"));

    bwidth = bbox->w / height;

    switch (format)
    {
        default:
        case rt2dJUSTIFYLEFT:
            justscale = ((RwReal) 0);
            break;

        case rt2dJUSTIFYCENTER:
            justscale = height * 0.5f;
            break;

        case rt2dJUSTIFYRIGHT:
            justscale = height;
            break;
    }

    if (font)
    {
        Rt2dFontChar       *tmpstring, *substring;
        Rt2dFontChar       *lastwordbreak;
        RwReal              width, breakwidth = ((RwReal) 0);
        RwV2d               anchor;
        RwBool              bFirstWhiteSpace;

        tmpstring = (Rt2dFontChar *) string;
        substring = (Rt2dFontChar *) string;
        lastwordbreak = (Rt2dFontChar *) string;
        bFirstWhiteSpace = TRUE;
        width = ((RwReal) 0);
        anchor.y = bbox->y + bbox->h - height;

        while (*tmpstring)
        {
            rt2dCharRect       *cr = &font->map[(RwUInt32) * tmpstring];

            if (bFirstWhiteSpace && RwIsSpace(*tmpstring))
            {
                bFirstWhiteSpace = FALSE;
                lastwordbreak = tmpstring;
                breakwidth = width;
            }
            else
            {
                bFirstWhiteSpace = TRUE;

                if (*tmpstring == RWSTRING('\n'))
                {
                    lastwordbreak = tmpstring + 1;
                    breakwidth = width;
                }
            }

            width += cr->width + font->intergap;
            if (width > bwidth || *tmpstring == RWSTRING('\n'))
            {
                Rt2dFontChar        savech;

                /* show substring */
                savech = *lastwordbreak;
                *lastwordbreak = (RwChar) (0);
                anchor.x = bbox->x + (bwidth - breakwidth) * justscale;

                font->fontshowCB(font, (RwChar *) substring,
                                 height, &anchor, brush);

                *lastwordbreak = savech;

                /* eat white space */
                while (RwIsSpace(*lastwordbreak))
                {
                    lastwordbreak++;
                }
                substring = lastwordbreak;
                tmpstring = lastwordbreak;

                /* bump to the next line */
                bFirstWhiteSpace = TRUE;
                width = ((RwReal) 0);
                anchor.y -= height;

                if (anchor.y < bbox->y)
                {
                    RWRETURN((Rt2dFont *)NULL);
                }
            }
            else
            {
                tmpstring++;
            }
        }

        anchor.x = bbox->x + (bwidth - width) * justscale;

        font->fontshowCB(font, (RwChar *) substring, height, &anchor,
                         brush);
    }

    RWRETURN(font);
}

/****************************************************************************/

/**
 * \ingroup rt2d
 * \ref Rt2dFontDestroy
 * is used to destroy the specified font. All fonts
 * created by an application must be destroyed before the application
 * closes down.
 *
 * The include file rt2d.h and the library file rt2d.lib are required to
 * use this function.
 * \param font  Pointer to the font.
 * \return TRUE if successful or FALSE if there is an error.
 * \see Rt2dFontRead
 * \see Rt2dOpen
 * \see Rt2dClose
 */
RwBool
Rt2dFontDestroy(Rt2dFont * font)
{
    RWAPIFUNCTION(RWSTRING("Rt2dFontDestroy"));

    if (font)
    {
        RwUInt32            nChar;

        for (nChar = 0; nChar < rtFONTCHARCOUNT; nChar++)
        {
            rt2dCharRect       *map = &font->map[nChar];

            if (NULL != map->path)
            {
                Rt2dPathDestroy(map->path);
                map->path = (Rt2dPath *)NULL;
            }
        }

        /* destroy the texture dictionary */
        if (font->texdict)
        {
            RwTexDictionaryDestroy(font->texdict);
        }

        RwFreeListFree(Rt2dGlobals.fontFreeList, font);

        RWRETURN(TRUE);
    }

    RWRETURN(FALSE);
}

/**
 * \ingroup rt2d
 * \ref Rt2dFontRead
 * is used to create a font by reading the information
 * contained in the specified font metrics file (the file extension
 * .met is assumed but it should not be included in the file
 * name). The path specified using the function Rt2dFontSetPath is used
 * to indicate the location of the file in the file system. Internally,
 * the full pathname of the font file is obtained by concatenating the
 * font search path with the supplied file name and appending the
 * extension .met (note that the search path should including a
 * trailing path separator).
 *
 * A side effect of this function is that the image path is changed.
 *
 * The include file rt2d.h and the library file rt2d.lib are required to
 * use this function.
 * \param name  Pointer to a string containing the name of the font file.
 * \return a pointer to the font if successful or NULL if there is an
 * error.
 * \see Rt2dFontSetPath
 * \see Rt2dFontShow
 * \see Rt2dFontFlow
 * \see Rt2dFontDestroy
 * \see RwImageSetPath
 */
Rt2dFont           *
Rt2dFontRead(const RwChar * name)
{
    Rt2dFont           *font;
    RwFileFunctions    *fileFuncs;

    RWAPIFUNCTION(RWSTRING("Rt2dFontRead"));

    fileFuncs = RwOsGetFileInterface();
    if (!fileFuncs)
    {
        RWRETURN((Rt2dFont *)NULL);
    }

    font = FontCreate2d();
    if (!font)
    {
        RWRETURN((Rt2dFont *)NULL);
    }
    else
    {
        RwStringFilename    filename;
        void               *fp;

        rwsprintf(filename, RWSTRING("%s%s.met"),
                  Rt2dGlobals.fontPath, name);

        fp = fileFuncs->rwfopen(filename, RWSTRING("r"));

        if (fp)
        {
            RwStringLine        line;

            fileFuncs->rwfgets(&line[0], sizeof(line), fp);

            if (!rwstrcmp(&line[0], RWSTRING("METRICS1\n")))
            {
                font =
                    FontReadMetrics2d1(font, fileFuncs,
                                       &filename, fp, &line);

            }
            else if (!rwstrcmp(&line[0], RWSTRING("METRICS2\n")))
            {
                font =
                    FontReadMetrics2d2(font, fileFuncs,
                                       &filename, fp, &line);
            }
            else if (!rwstrcmp(&line[0], RWSTRING("METRICS3\n")))
            {
                font =
                    FontReadMetrics2d3(font, fileFuncs,
                                       &filename, fp, &line);
            }

            fileFuncs->rwfclose(fp);
        }
        else
        {
            Rt2dFontDestroy(font);

            font = (Rt2dFont *)NULL;
        }
    }

    RWRETURN(font);
}

/**
 * \ingroup rt2d
 * \ref Rt2dFontShow
 * is used to render the specified string using the given
 * font and brush. The height and anchor parameters specify the size and
 * position of the string text when it is rendered; the anchor defines
 * the position of lower-left corner of the string's bounding-box.
 *
 * Note that heights and positions are defined in absolute coordinates
 * and are subject to the current transformation matrix (CTM).
 *
 * The include file rt2d.h and the library file rt2d.lib are required to
 * use this function.
 * \param font  Pointer to the font.
 * \param string  Pointer to the string.
 * \param height  A RwReal value equal to the height of the rendered text.
 * \param anchor  A RwV2d value specifying the position for the rendered text.
 * \param brush  Pointer to the brush.
 * \return a pointer to the font if successful or NULL if there is an
 * error.
 * \see Rt2dFontRead
 * \see Rt2dFontFlow
 * \see Rt2dFontGetHeight
 * \see Rt2dFontGetStringWidth
 */
Rt2dFont           *
Rt2dFontShow(Rt2dFont * font,
             const RwChar * string,
             RwReal height, RwV2d * anchor, Rt2dBrush * brush)
{
    RWAPIFUNCTION(RWSTRING("Rt2dFontShow"));

    RWRETURN(font->fontshowCB(font, string, height, anchor, brush));
}

/****************************************************************************/

/**
 * \ingroup rt2d
 * \ref Rt2dFontSetIntergapSpacing
 * is used to define the horizontal spacing of
 * characters when a string is rendered using the specified
 * font. Essentially, an application can use this function to control the
 * spread of rendered strings.
 *
 * The default spacing is zero, that is, no spreading.
 *
 * The include file rt2d.h and the library file rt2d.lib are required to
 * use this function.
 *
 *
 * \param font  A pointer to the font.
 * \param gap  A RwReal value equal to the intergap spacing.
 * \return a pointer to the font if successful or NULL if there is an
 * error.
 * \see Rt2dFontGetHeight
 * \see Rt2dFontGetStringWidth
 */
Rt2dFont           *
Rt2dFontSetIntergapSpacing(Rt2dFont * font, RwReal gap)
{
    RWAPIFUNCTION(RWSTRING("Rt2dFontSetIntergapSpacing"));

    RWASSERT(font);

    font->intergap = gap;

    RWRETURN(font);
}

/****************************************************************************/

/**
 * \ingroup rt2d
 * \ref Rt2dFontCacheFlush
 * is used to flush the string cache. This forces the rendering of all the
 * strings in the cache.
 *
 * Strings using a simple single bitmap as a font are not rendered immediately
 * but instead are store in a cache. This enables all the strings using the
 * same font to be rendered together rather than seperately to give an
 * improvement in performance.
 *
 * This means the drawing order for strings are not preserve and will appear in
 * front of other 2d objects.
 *
 * Normally the cache is flush automatically via \ref RwCameraEndUpdate or when
 * the cache is full.
 *
 * The include file rt2d.h and the library file rt2d.lib are required to
 * use this function.
 *
 * \note This function is supported on PS2 only.
 *
 * \return TRUE  if successful or FALSE if there is an error.
 * \see Rt2dFontFlow
 * \see Rt2dFontShow
 */
RwBool
Rt2dFontCacheFlush( void )
{
    RWAPIFUNCTION(RWSTRING("Rt2dFontCacheFlush"));

#ifdef SKY2_DRVMODEL_H

    _rt2dPS2SingleFontFlush();

#endif /* SKY2_DRVMODEL_H */

    RWRETURN(TRUE);
}

