/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
///////////////////////////////////////////////////////////////////////////////
//                                                                           //
//  plDynSurfaceWriter Class Header                                          //
//  Abstract class wrapping around Windows GDI functionality for writing to  //
//  a generic RGBA surface. Allows us to create one writer per DTMap or a    //
//  single shared writer to conserve OS resources on 98/ME.                  //
//                                                                           //
//  Cyan, Inc.                                                               //
//                                                                           //
//// Version History //////////////////////////////////////////////////////////
//                                                                           //
//  10.28.2002 mcn - Created.                                                //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

#include "hsTypes.h"
#include "hsWindows.h"
#include "plDynSurfaceWriter.h"

#include "plDynamicTextMap.h"
#include "hsExceptions.h"
#include "hsUtils.h"
#include "hsMatrix44.h"
#include "plMessage/plDynamicTextMsg.h"
#include "pnKeyedObject/plKey.h"
#include "plProfile.h"
#include "plStatusLog/plStatusLog.h"
#include "plWinFontCache.h"


//// plWinSurface Helper Functions ////////////////////////////////////////////

#if HS_BUILD_FOR_WIN32

static UInt32       sNumDCsAllocated;
static UInt32       sNumBitmapsAllocated;

plDynSurfaceWriter::plWinSurface::plWinSurface()
{
    fDC = nil;
    fBitmap = nil;
    fFont = nil;
    fBits = nil;
    fTextColor = RGB( 255, 255, 255 );
    fWidth = fHeight = 0;

    fSaveNum = 0;
    fFontFace = nil;
    fFontSize = 0;
    fFontFlags = 0;
    fFontAntiAliasRGB = false;
    fFontBlockedRGB = false;
}

plDynSurfaceWriter::plWinSurface::~plWinSurface()
{
    Release();
}

void    plDynSurfaceWriter::plWinSurface::Allocate( UInt16 w, UInt16 h )
{
    int         i;
    BITMAPINFO  *bmi;


    Release();

    fWidth = w;
    fHeight = h;

    /// Initialize a bitmap info struct to describe our surface
    if( IBitsPerPixel() == 8 )
        bmi = (BITMAPINFO *)( TRACKED_NEW UInt8[ sizeof( BITMAPINFOHEADER ) + sizeof( RGBQUAD ) * 256 ] );
    else
        bmi = TRACKED_NEW BITMAPINFO;

    memset( &bmi->bmiHeader, 0, sizeof( BITMAPINFOHEADER ) );
    bmi->bmiHeader.biSize = sizeof( BITMAPINFOHEADER );
    bmi->bmiHeader.biWidth = (int)fWidth;
    bmi->bmiHeader.biHeight = -(int)fHeight;
    bmi->bmiHeader.biPlanes = 1;
    bmi->bmiHeader.biCompression = BI_RGB;
    bmi->bmiHeader.biBitCount = IBitsPerPixel();
    if( IBitsPerPixel() == 8 )
    {
        // Set up map for grayscale bitmap
        for( i = 0; i < 256; i++ )
        {
            bmi->bmiColors[ i ].rgbRed = i;
            bmi->bmiColors[ i ].rgbGreen = i;
            bmi->bmiColors[ i ].rgbBlue = i;
            bmi->bmiColors[ i ].rgbReserved = i;
        }
    }
    
    /// Create a screen-compatible DC
    fDC = CreateCompatibleDC( nil );
    if( fDC == nil )
    {
        char msg[ 256 ];
        FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nil, GetLastError(), MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), msg, sizeof( msg ), nil );
        char *ret = strrchr( msg, '\n' );
        if( ret != nil )
            *ret = 0;

        plStatusLog::AddLineS( "pipeline.log", 0xffff0000, "Unable to allocate DC for dynamic text map (%s, %d DCs allocated already)", msg, sNumDCsAllocated );
        if (IBitsPerPixel() == 8 )
            delete [] bmi;
        else
            delete bmi;
        return;
    }
    sNumDCsAllocated++;

    /// Create a bitmap using the DC and the bitmapInfo struct we filled out
    fBitmap = CreateDIBSection( fDC, bmi, DIB_RGB_COLORS, (void **)&fBits, nil, 0 );
    if( fBitmap == nil )
    {
        char msg[ 256 ];
        FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nil, GetLastError(), MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), msg, sizeof( msg ), nil );
        char *ret = strrchr( msg, '\n' );
        if( ret != nil )
            *ret = 0;

        plStatusLog::AddLineS( "pipeline.log", 0xffff0000, "Unable to allocate RGB DIB section for dynamic text map (%s, %d bitmaps allocated already)", msg, sNumBitmapsAllocated );
        if (IBitsPerPixel() == 8 )
            delete [] bmi;
        else
            delete bmi;
        return;
    }
    sNumBitmapsAllocated++;

    /// Set up some basic props
    SetMapMode( fDC, MM_TEXT );
    SetBkMode( fDC, TRANSPARENT );
    SetTextAlign( fDC, TA_TOP | TA_LEFT );

    fSaveNum = SaveDC( fDC );
    
    SelectObject( fDC, fBitmap );

    if (IBitsPerPixel() == 8 )
        delete [] bmi;
    else
        delete bmi;
}

void    plDynSurfaceWriter::plWinSurface::Release( void )
{
    if( fBitmap != nil )
        sNumBitmapsAllocated--;
    if( fDC != nil )
        sNumDCsAllocated--;

    if( fSaveNum != 0 )
        RestoreDC( fDC, fSaveNum );
    fSaveNum = 0;

    DeleteObject( fBitmap );
    DeleteDC( fDC );

    fDC = nil;
    fBitmap = nil;
    fFont = nil;
    fBits = nil;
    fWidth = fHeight = 0;

    delete [] fFontFace;
    fFontFace = nil;
    fFontSize = 0;
    fFontFlags = 0;
    fFontAntiAliasRGB = false;
    fFontBlockedRGB = false;
}

hsBool  plDynSurfaceWriter::plWinSurface::WillFit( UInt16 w, UInt16 h )
{
    if( fWidth >= w && fHeight >= h )
        return true;
    return false;
}

static int      SafeStrCmp( const char *str1, const char *str2 )
{
    if( str1 == nil && str2 == nil )
        return -1;
    if( str1 != nil && str2 != nil )
        return strcmp( str1, str2 );
    return -1;
}

hsBool  plDynSurfaceWriter::plWinSurface::FontMatches( const char *face, UInt16 size, UInt8 flags, hsBool aaRGB )
{
    if( SafeStrCmp( face, fFontFace ) == 0 && fFontSize == size && 
        fFontFlags == flags && fFontAntiAliasRGB == aaRGB )
        return true;
    
    return false;
}

void    plDynSurfaceWriter::plWinSurface::SetFont( const char *face, UInt16 size, UInt8 flags, hsBool aaRGB )
{
    delete [] fFontFace;
    fFontFace = ( face != nil ) ? hsStrcpy( face ) : nil;
    fFontSize = size;
    fFontFlags = flags;
    fFontAntiAliasRGB = aaRGB;

    bool hadAFont = false;
    if( fFont != nil )
    {
        hadAFont = true;
        plWinFontCache::GetInstance().FreeFont( fFont );    
        fFont = nil;
    }

    if( face == nil )
        return;

    bool    bold = ( fFontFlags & plDynSurfaceWriter::kFontBold ) ? true : false;
    bool    italic = ( fFontFlags & plDynSurfaceWriter::kFontItalic ) ? true : false;

    int nHeight = -MulDiv( size, GetDeviceCaps( fDC, LOGPIXELSY ), 72 );
    fFont = plWinFontCache::GetInstance().GetMeAFont( face, nHeight, bold ? FW_BOLD : FW_NORMAL, italic, 
                                                        fFontAntiAliasRGB ? ANTIALIASED_QUALITY : DEFAULT_QUALITY );
    if( fFont == nil && fFontAntiAliasRGB )
    {
        static bool warnedCantAntiAlias = false;

        // Creation of font failed; could be that we can't do anti-aliasing? Try not doing it...
        if( !warnedCantAntiAlias )
        {
            plStatusLog::AddLineS( "pipeline.log", "WARNING: Cannot allocate anti-aliased font. Falling back to non-anti-aliased. This will be the only warning" );
            warnedCantAntiAlias = true;
        }

        fFont = plWinFontCache::GetInstance().GetMeAFont( face, nHeight, bold ? FW_BOLD : FW_NORMAL, italic, 
                                                            fFontAntiAliasRGB ? ANTIALIASED_QUALITY : DEFAULT_QUALITY );
    }

    if( fFont == nil )
    {
        hsAssert( false, "Cannot create Windows font for plDynSurfaceWriter" );
        plStatusLog::AddLineS( "pipeline.log", "ERROR: Cannot allocate font for RGB surface! (face: %s, size: %d %s %s)", face, nHeight, bold ? "bold" : "", italic ? "italic" : "" );

        delete [] fFontFace;
        fFontFace = nil;
        fFontSize = 0;
        return;
    }

    if( SelectObject( fDC, fFont ) == nil && hadAFont )
    {
        char msg[ 256 ];
        FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nil, GetLastError(), MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), msg, sizeof( msg ), nil );
        char *ret = strrchr( msg, '\n' );
        if( ret != nil )
            *ret = 0;

        plStatusLog::AddLineS( "pipeline.log", 0xffff0000, "SelectObject() FAILED (%s)", msg );
    }

}

#endif // BUILD_FOR_WIN32

//// StupidStatic /////////////////////////////////////////////////////////////

hsBool  plDynSurfaceWriter::fForceSharedSurfaces = false;
hsBool  plDynSurfaceWriter::fOSDetected = false;
hsBool  plDynSurfaceWriter::fOSCanShareSurfaces = false;

hsBool  plDynSurfaceWriter::CanHandleLotsOfThem( void )
{
    if( fOSDetected )
        return fOSCanShareSurfaces;

    fOSDetected = true;

#if HS_BUILD_FOR_WIN32
    OSVERSIONINFO   versionInfo;
    memset( &versionInfo, 0, sizeof( versionInfo ) );
    versionInfo.dwOSVersionInfoSize = sizeof( versionInfo );

    if( GetVersionEx( &versionInfo ) )
    {
        plStatusLog::AddLineS( "pipeline.log", "OS version detection results:" );
        plStatusLog::AddLineS( "pipeline.log", "   Version: %d.%d", versionInfo.dwMajorVersion, versionInfo.dwMinorVersion );
        plStatusLog::AddLineS( "pipeline.log", "   Build #: %d", versionInfo.dwBuildNumber );
        plStatusLog::AddLineS( "pipeline.log", "   Platform ID: %d", versionInfo.dwPlatformId );

        if( versionInfo.dwPlatformId == VER_PLATFORM_WIN32_NT )
        {
            if( fForceSharedSurfaces )
            {
                plStatusLog::AddLineS( "pipeline.log", "Detected NT-based platform, but sharing surfaces due to override" );
                fOSCanShareSurfaces = false;
            }
            else
            {
                plStatusLog::AddLineS( "pipeline.log", "Detected NT-based platform, allowing separate surfaces" );
                fOSCanShareSurfaces = true;
            }
        }
        else
        {
            plStatusLog::AddLineS( "pipeline.log", "Detected non-NT-based platform: sharing surfaces" );
            fOSCanShareSurfaces = false;
        }
    }
    else
    {
        plStatusLog::AddLineS( "pipeline.log", "OS version detection failed" );
        fOSCanShareSurfaces = false;
    }

#endif
    return fOSCanShareSurfaces;
}

//// Constructor & Destructor /////////////////////////////////////////////////

plDynSurfaceWriter::plDynSurfaceWriter()
{
    IInit();
}

plDynSurfaceWriter::~plDynSurfaceWriter()
{
    Reset();
}

plDynSurfaceWriter::plDynSurfaceWriter( plDynamicTextMap *target, UInt32 flags )
{
    IInit();
    fFlags = flags;
    SwitchTarget( target );
}

void    plDynSurfaceWriter::IInit( void )
{
    fCurrTarget = 0;
    fJustify = kLeftJustify;
    fFlags = 0;
    fFlushed = true;
    fFontFace = nil;
    fFontSize = 0;
    fFontBlockedRGB = false;
}

//// Reset ////////////////////////////////////////////////////////////////////

void    plDynSurfaceWriter::Reset( void )
{
    fRGBSurface.Release();
    fAlphaSurface.Release();
    fCurrTarget = nil;
    fFlushed = true;

    delete [] fFontFace;
    fFontFace = nil;
    fFontSize = 0;
}


///////////////////////////////////////////////////////////////////////////////
//// Target Switching /////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

//// FlushToTarget ////////////////////////////////////////////////////////////
//  Flushes all ops to the target.

void    plDynSurfaceWriter::FlushToTarget( void )
{
    int     x, y;


    if( fCurrTarget != nil && !fFlushed )
    {
#if HS_BUILD_FOR_WIN32
        // Flush the GDI so we can grab the bits
        GdiFlush();

        UInt32 *destBits = (UInt32 *)fCurrTarget->GetImage();

        // Are we merging in the alpha bits?
        if( fFlags & kSupportAlpha )
        {
            // Yup, munge 'em
            UInt32  *srcRGBBits = fRGBSurface.GetBits();
            UInt8   *srcAlphaBits = fAlphaSurface.GetBits();
            UInt32  destWidth = fCurrTarget->GetWidth();

            for( y = 0; y < fCurrTarget->GetHeight(); y++ )
            {
                for( x = 0; x < destWidth; x++ )
                    destBits[ x ] = ( srcRGBBits[ x ] & 0x00ffffff ) | ( (UInt32)srcAlphaBits[ x ] << 24 );

                destBits += destWidth;
                srcRGBBits += fRGBSurface.fWidth;
                srcAlphaBits += fAlphaSurface.fWidth;
            }
        }
        else
        {
            // Nope, just a 24-bit copy and set alphas to ff
            UInt32  *srcBits = fRGBSurface.GetBits();
            UInt32  destWidth = fCurrTarget->GetWidth();

            for( y = 0; y < fCurrTarget->GetHeight(); y++ )
            {
                memcpy( destBits, srcBits, destWidth * sizeof( UInt32 ) );

                // Fill in 0xff
                for( x = 0; x < destWidth; x++ )
                    destBits[ x ] |= 0xff000000;

                destBits += destWidth;
                srcBits += fRGBSurface.fWidth;
            }
        }
#endif
    }
    fFlushed = true;
}

//// SwitchTarget /////////////////////////////////////////////////////////////
//  Switches targets. Will flush to old target before switching. Also, if 
//  kDiscard isn't specified, will copy contents of new target to working 
//  surface.

void    plDynSurfaceWriter::SwitchTarget( plDynamicTextMap *target )
{
    if( target == fCurrTarget )
        return;

    if( !fFlushed )
        FlushToTarget();

    fCurrTarget = target;
    fFlushed = true;        // Will force a copy next IEnsureSurfaceUpdated()

    // Make sure our surfaces fit
    bool hadToAllocate = false;
    if( target != nil )
    {
        if( !fRGBSurface.WillFit( (UInt16)(target->GetWidth()), (UInt16)(target->GetHeight()) ) )
        {
            fRGBSurface.Allocate( (UInt16)(target->GetWidth()), (UInt16)(target->GetHeight()) );
            hadToAllocate = true;
        }

        if( fFlags & kSupportAlpha )
        {
            if( !fAlphaSurface.WillFit( (UInt16)(target->GetWidth()), (UInt16)(target->GetHeight()) ) ) 
            {
                fAlphaSurface.Allocate( (UInt16)(target->GetWidth()), (UInt16)(target->GetHeight()) );
                hadToAllocate = true;
            }
        }
    }
    else
    {
        fRGBSurface.Release();
        fAlphaSurface.Release();
        hadToAllocate = true;
    }

    if( hadToAllocate )
    {
        delete [] fFontFace;
        fFontFace = nil;
        fFontSize = 0;
        fFontFlags = 0;
    }
}

//// IEnsureSurfaceUpdated ////////////////////////////////////////////////////
//  Makes sure our surfaces are ready to write to.

void    plDynSurfaceWriter::IEnsureSurfaceUpdated( void )
{
    UInt32  x, y;


    // If we're flushed, then we haven't drawn since the last flush,
    // which means we want to copy from our target before we start drawing.
    // If we've already drawn, then we won't be flushed and we don't want
    // to be copying over what we've already drawn
    if( fCurrTarget != nil && fFlushed )
    {
        UInt32 *srcBits = (UInt32 *)fCurrTarget->GetImage();

        // Are we merging in the alpha bits?
        if( fFlags & kSupportAlpha )
        {
            // Yup, de-munge 'em
            UInt32  *destRGBBits = fRGBSurface.GetBits();
            UInt8   *destAlphaBits = fAlphaSurface.GetBits();
            UInt32  srcWidth = fCurrTarget->GetWidth();

            for( y = 0; y < fCurrTarget->GetHeight(); y++ )
            {
                for( x = 0; x < srcWidth; x++ )
                {
                    destRGBBits[ x ] = srcBits[ x ];    // Windows GDI probably doesn't care about the alpha bits here. Hopefully...
                    destAlphaBits[ x ] = (UInt8)(srcBits[ x ] >> 24);
                }

                srcBits += srcWidth;
                destRGBBits += fRGBSurface.fWidth;
                destAlphaBits += fAlphaSurface.fWidth;
            }
        }
        else
        {
            // Nope, just do a straight memcopy
            UInt32  *destBits = fRGBSurface.GetBits();
            UInt32  srcWidth = fCurrTarget->GetWidth();

            for( y = 0; y < fCurrTarget->GetHeight(); y++ )
            {
                memcpy( destBits, srcBits, srcWidth * sizeof( UInt32 ) );

                srcBits += srcWidth;
                destBits += fRGBSurface.fWidth;
            }
        }       

        // ALSO, we need to re-update our settings, since different targets
        // can have different fonts or justifications
        ISetFont( fCurrTarget->GetFontFace(), fCurrTarget->GetFontSize(), 0/*fCurrTarget->GetWriterFontFlags()*/, fCurrTarget->GetFontAARGB() );
        SetJustify( (Justify)fCurrTarget->GetFontJustify() );
        ISetTextColor( fCurrTarget->GetFontColor(), fCurrTarget->GetFontBlockRGB() );

        fFlushed = false;
    }
}

hsBool  plDynSurfaceWriter::IsValid( void ) const
{
    if( fCurrTarget == nil )
        return false;

    if( fRGBSurface.fDC == nil || fRGBSurface.fBitmap == nil )
        return false;

    if( ( fFlags & kSupportAlpha ) && ( fAlphaSurface.fDC == nil || fAlphaSurface.fBitmap == nil ) )
        return false;

    return true;
}

///////////////////////////////////////////////////////////////////////////////
//// Rendering Functions //////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

//// SetBitsFromBuffer ////////////////////////////////////////////////////////

/*
void    plDynSurfaceWriter::SetBitsFromBuffer( UInt32 *clearBuffer, UInt16 width, UInt16 height )
{
    int         x, y;
    UInt32      *data = (UInt32 *)fImage, *srcData = clearBuffer;
    UInt8       *destAlpha = nil;


    if( !IsValid() )
        return;

#if HS_BUILD_FOR_WIN32
    GdiFlush();
#endif

    // Clear *all* to zero
    memset( data, 0, fWidth * fHeight * sizeof( UInt32 ) );
    if( fHasAlpha )
    {
#if HS_BUILD_FOR_WIN32
        memset( fWinAlphaBits, 0, fWidth * fHeight );
        destAlpha = fWinAlphaBits;
#endif
    }

    // Buffer is of size fVisWidth x fVisHeight, so we need a bit of work to do this right
    for( y = 0; y < fVisHeight; y++ )
    {
        for( x = 0; x < fVisWidth; x++ )
        {
            data[ x ] = srcData[ x ];
        }

        if( destAlpha != nil )
        {
            for( x = 0; x < fVisWidth; x++ )
                destAlpha[ x ] = srcData[ x ] >> 24;

            destAlpha += fWidth;
        }

        data += fWidth;
        srcData += fVisWidth;
    }
}
*/

//// ClearToColor /////////////////////////////////////////////////////////////

void    plDynSurfaceWriter::ClearToColor( hsColorRGBA &color )
{
    if( !IsValid() )
        return;

    IEnsureSurfaceUpdated();

#if HS_BUILD_FOR_WIN32

    UInt32      i, hexColor = color.ToARGB32();


    // Flush the GDI first, so it doesn't decide to overwrite us later
    GdiFlush();

    UInt32 *rgbBits = fRGBSurface.GetBits();
    for( i = 0; i < fRGBSurface.fWidth * fRGBSurface.fHeight; i++ )
        rgbBits[ i ] = hexColor;

    if( fFlags & kSupportAlpha )
    {
        UInt8 *alphaBits = fAlphaSurface.GetBits(), alpha = (UInt8)(hexColor >> 24);

        for( i = 0; i < fAlphaSurface.fWidth * fAlphaSurface.fHeight; i++ )
            alphaBits[ i ] = alpha;
    }
#endif
}

//// SetFont //////////////////////////////////////////////////////////////////
//  OS-specific. Load the given font for drawing the text with.

void    plDynSurfaceWriter::SetFont( const char *face, UInt16 size, UInt8 fontFlags, hsBool antiAliasRGB )
{
    if( !IsValid() )
        return;

    IEnsureSurfaceUpdated();

    ISetFont( face, size, fontFlags, antiAliasRGB );
}

//// ISetFont /////////////////////////////////////////////////////////////////

void    plDynSurfaceWriter::ISetFont( const char *face, UInt16 size, UInt8 fontFlags, hsBool antiAliasRGB )
{
    fFlags = ( fFlags & ~kFontShadowed ) | ( fontFlags & kFontShadowed );

    if( !fRGBSurface.FontMatches( face, size, fontFlags, antiAliasRGB ) )
        fRGBSurface.SetFont( face, size, fontFlags, antiAliasRGB );

    if( fFlags & kSupportAlpha )
    {
        if( !fAlphaSurface.FontMatches( face, size, fontFlags, !antiAliasRGB ) )
            fAlphaSurface.SetFont( face, size, fontFlags, !antiAliasRGB );
    }
}

//// SetTextColor /////////////////////////////////////////////////////////////
//  blockRGB basically forces the RGB channel to write in blocks instead of
//  actual characters. This isn't useful unless you're relying on the alpha
//  channel to do the text (opaque text and transparent background), in which
//  case you want plenty of block color in your RGB channel because it'll get
//  alpha-ed out by the alpha channel.

void    plDynSurfaceWriter::SetTextColor( hsColorRGBA &color, hsBool blockRGB )
{
    if( !IsValid() )
        return;

    IEnsureSurfaceUpdated();
    ISetTextColor( color, blockRGB );
}

//// IRefreshTextColor ////////////////////////////////////////////////////////

void    plDynSurfaceWriter::ISetTextColor( hsColorRGBA &color, hsBool blockRGB )
{
#if HS_BUILD_FOR_WIN32

    int r = (int)(color.r * 255.f);
    int g = (int)(color.g * 255.f);
    int b = (int)(color.b * 255.f);

    fRGBSurface.fTextColor = RGB( r, g, b );
    if( fFlags & kSupportAlpha )
    {
        int a = (int)(color.a * 255.f);
        fAlphaSurface.fTextColor = RGB( a, a, a );
    }

    fFontBlockedRGB = blockRGB;

    if( fFontBlockedRGB && !( fFlags & kFontShadowed ) )
    {
        ::SetBkColor( fRGBSurface.fDC, fRGBSurface.fTextColor );
        ::SetBkMode( fRGBSurface.fDC, OPAQUE );
    }
    else
        ::SetBkMode( fRGBSurface.fDC, TRANSPARENT );

    ::SetTextColor( fRGBSurface.fDC, fRGBSurface.fTextColor );
    
    if( fFlags & kSupportAlpha )
        ::SetTextColor( fAlphaSurface.fDC, fAlphaSurface.fTextColor );
#endif
}

//// SetJustify ///////////////////////////////////////////////////////////////

void    plDynSurfaceWriter::SetJustify( Justify j )
{
    fJustify = j;
}

//// IRefreshOSJustify ////////////////////////////////////////////////////////

void    plDynSurfaceWriter::IRefreshOSJustify( void )
{
    if( !IsValid() )
        return;

    UINT    justMode;
    switch( fJustify )
    {
        case kLeftJustify:  justMode = TA_LEFT; break;
        case kCenter:       justMode = TA_CENTER; break;
        case kRightJustify: justMode = TA_RIGHT; break;
    }
    ::SetTextAlign( fRGBSurface.fDC, justMode );
    if( fFlags & kSupportAlpha )
        ::SetTextAlign( fAlphaSurface.fDC, justMode );
}

//// DrawString ///////////////////////////////////////////////////////////////

void    plDynSurfaceWriter::DrawString( UInt16 x, UInt16 y, const char *text )
{
    if( !IsValid() )
        return;

    IEnsureSurfaceUpdated();

    IRefreshOSJustify();

#if HS_BUILD_FOR_WIN32
    if( fFlags & kFontShadowed )
    {
        ::SetTextColor( fRGBSurface.fDC, RGB( 0, 0, 0 ) );
        ::TextOut( fRGBSurface.fDC, x + 1, y + 1, text, strlen( text ) );

        ::SetTextColor( fRGBSurface.fDC, fRGBSurface.fTextColor );
        ::TextOut( fRGBSurface.fDC, x, y, text, strlen( text ) );

        if( fFlags & kSupportAlpha )
        {
            ::TextOut( fAlphaSurface.fDC, x + 1, y + 1, text, strlen( text ) );
            ::TextOut( fAlphaSurface.fDC, x, y, text, strlen( text ) );
        }
    }
    else
    {
        ::TextOut( fRGBSurface.fDC, x, y, text, strlen( text ) );
        if( fFlags & kSupportAlpha )
            ::TextOut( fAlphaSurface.fDC, x, y, text, strlen( text ) );
    }

#endif
}

//// DrawClippedString ////////////////////////////////////////////////////////

void    plDynSurfaceWriter::DrawClippedString( Int16 x, Int16 y, const char *text, UInt16 width, UInt16 height )
{
    if( !IsValid() )
        return;

    IEnsureSurfaceUpdated();

    IRefreshOSJustify();

#if HS_BUILD_FOR_WIN32
    
    RECT    r;
    ::SetRect( &r, x, y, x + width, y + height );

    if( fJustify == kRightJustify )
        x += width - 1;
    else if( fJustify == kCenter )
        x += width >> 1;

    if( fFlags & kFontShadowed )
    {
        ::SetTextColor( fRGBSurface.fDC, RGB( 0, 0, 0 ) );

        ::OffsetRect( &r, 1, 1 );
        ::ExtTextOut( fRGBSurface.fDC, x + 1, y + 1, ETO_CLIPPED, &r, text, strlen( text ), nil );
        if( fFlags & kSupportAlpha )
            ::ExtTextOut( fAlphaSurface.fDC, x + 1, y + 1, ETO_CLIPPED, &r, text, strlen( text ), nil );
        ::OffsetRect( &r, -1, -1 );

        ::SetTextColor( fRGBSurface.fDC, fRGBSurface.fTextColor );
    }

    ::ExtTextOut( fRGBSurface.fDC, x, y, ETO_CLIPPED, &r, text, strlen( text ), nil );
    if( fFlags & kSupportAlpha )
        ::ExtTextOut( fAlphaSurface.fDC, x, y, ETO_CLIPPED, &r, text, strlen( text ), nil );
    
#endif
}

//// DrawClippedString ////////////////////////////////////////////////////////

void    plDynSurfaceWriter::DrawClippedString( Int16 x, Int16 y, const char *text, UInt16 clipX, UInt16 clipY, UInt16 width, UInt16 height )
{
    if( !IsValid() )
        return;

    IEnsureSurfaceUpdated();

    IRefreshOSJustify();

#if HS_BUILD_FOR_WIN32
    
    RECT    r;
    ::SetRect( &r, clipX, clipY, clipX + width, clipY + height );

    if( fFlags & kFontShadowed )
    {
        ::SetTextColor( fRGBSurface.fDC, RGB( 0, 0, 0 ) );

        ::OffsetRect( &r, 1, 1 );
        ::ExtTextOut( fRGBSurface.fDC, x + 1, y + 1, ETO_CLIPPED, &r, text, strlen( text ), nil );
        if( fFlags & kSupportAlpha )
            ::ExtTextOut( fAlphaSurface.fDC, x + 1, y + 1, ETO_CLIPPED, &r, text, strlen( text ), nil );
        ::OffsetRect( &r, -1, -1 );

        ::SetTextColor( fRGBSurface.fDC, fRGBSurface.fTextColor );
    }

    ::ExtTextOut( fRGBSurface.fDC, x, y, ETO_CLIPPED, &r, text, strlen( text ), nil );
    if( fFlags & kSupportAlpha )
        ::ExtTextOut( fAlphaSurface.fDC, x, y, ETO_CLIPPED, &r, text, strlen( text ), nil );

#endif
}

//// DrawWrappedString ////////////////////////////////////////////////////////

void    plDynSurfaceWriter::DrawWrappedString( UInt16 x, UInt16 y, const char *text, UInt16 width, UInt16 height )
{
    if( !IsValid() )
        return;

    IEnsureSurfaceUpdated();

#if HS_BUILD_FOR_WIN32
    
    RECT    r;
    ::SetRect( &r, x, y, x + width, y + height );

    UINT    format = DT_TOP | DT_NOPREFIX | DT_WORDBREAK;
    switch( fJustify )
    {
        case kLeftJustify:  format |= DT_LEFT; break;
        case kCenter:       format |= DT_CENTER; break;
        case kRightJustify: format |= DT_RIGHT; break;
    }

    if( fFlags & kFontShadowed )
    {
        ::SetTextColor( fRGBSurface.fDC, RGB( 0, 0, 0 ) );

        ::OffsetRect( &r, 1, 1 );
        ::DrawText( fRGBSurface.fDC, text, strlen( text ), &r, format );
        if( fFlags & kSupportAlpha )
            ::DrawText( fAlphaSurface.fDC, text, strlen( text ), &r, format );
        ::OffsetRect( &r, -1, -1 );

        ::SetTextColor( fRGBSurface.fDC, fRGBSurface.fTextColor );
    }

    ::DrawText( fRGBSurface.fDC, text, strlen( text ), &r, format );
    if( fFlags & kSupportAlpha )
        ::DrawText( fAlphaSurface.fDC, text, strlen( text ), &r, format );

#endif
}

//// CalcStringWidth //////////////////////////////////////////////////////////

UInt16      plDynSurfaceWriter::CalcStringWidth( const char *text, UInt16 *height )
{
    if( !IsValid() )
        return 0;

    IEnsureSurfaceUpdated();

#if HS_BUILD_FOR_WIN32

    SIZE size;
    ::GetTextExtentPoint32( fRGBSurface.fDC, text, strlen( text ), &size );

    if( height != nil )
        *height = (UInt16)size.cy;

    return (UInt16)size.cx;
#endif
}

//// CalcWrappedStringSize ////////////////////////////////////////////////////

void    plDynSurfaceWriter::CalcWrappedStringSize( const char *text, UInt16 *width, UInt16 *height )
{
    if( !IsValid() )
        return;

    IEnsureSurfaceUpdated();

#if HS_BUILD_FOR_WIN32

    RECT    r;
    ::SetRect( &r, 0, 0, *width, 0 );

    UINT    format = DT_TOP | DT_NOPREFIX | DT_WORDBREAK | DT_CALCRECT;
    switch( fJustify )
    {
        case kLeftJustify:  format |= DT_LEFT; break;
        case kCenter:       format |= DT_CENTER; break;
        case kRightJustify: format |= DT_RIGHT; break;
    }

    ::DrawText( fRGBSurface.fDC, text, strlen( text ), &r, format );

    *width = (UInt16)(r.right);
    if( height != nil )
        *height = (UInt16)r.bottom;
#endif
}

//// FillRect /////////////////////////////////////////////////////////////////

void    plDynSurfaceWriter::FillRect( UInt16 x, UInt16 y, UInt16 width, UInt16 height, hsColorRGBA &color )
{
    if( !IsValid() )
        return;

    IEnsureSurfaceUpdated();

#if HS_BUILD_FOR_WIN32

    RECT    rc;
    ::SetRect( &rc, x, y, x + width, y + height );

    int r = (int)(color.r * 255.f);
    int g = (int)(color.g * 255.f);
    int b = (int)(color.b * 255.f);
    int a = (int)(color.a * 255.f);

    HBRUSH brush = ::CreateSolidBrush( RGB( r, g, b ) );
    ::FillRect( fRGBSurface.fDC, &rc, brush );
    ::DeleteObject( brush );

    if( fFlags & kSupportAlpha )
    {
        brush = ::CreateSolidBrush( RGB( a, a, a ) );
        ::FillRect( fAlphaSurface.fDC, &rc, brush );
        ::DeleteObject( brush );
    }

#endif
}

//// FrameRect ////////////////////////////////////////////////////////////////

void    plDynSurfaceWriter::FrameRect( UInt16 x, UInt16 y, UInt16 width, UInt16 height, hsColorRGBA &color )
{
    if( !IsValid() )
        return;

    IEnsureSurfaceUpdated();

#if HS_BUILD_FOR_WIN32

    RECT    rc;
    ::SetRect( &rc, x, y, x + width, y + height );

    int r = (int)(color.r * 255.f);
    int g = (int)(color.g * 255.f);
    int b = (int)(color.b * 255.f);
    int a = (int)(color.a * 255.f);

    HBRUSH brush = ::CreateSolidBrush( RGB( r, g, b ) );
    ::FrameRect( fRGBSurface.fDC, &rc, brush );
    ::DeleteObject( brush );

    if( fFlags & kSupportAlpha )
    {
        brush = ::CreateSolidBrush( RGB( a, a, a ) );
        ::FrameRect( fAlphaSurface.fDC, &rc, brush );
        ::DeleteObject( brush );
    }

#endif
}

/*
//// DrawImage ////////////////////////////////////////////////////////////////

void    plDynSurfaceWriter::DrawImage( UInt16 x, UInt16 y, plMipmap *image, hsBool respectAlpha )
{
    if( !IsValid() )
        return;

#if HS_BUILD_FOR_WIN32
    // Flush the GDI first, to make sure it's done
    GdiFlush();
#endif

    plMipmap::CompositeOptions  opts( respectAlpha ? 0 : plMipmap::kForceOpaque );
    if( !fHasAlpha )
        opts.fFlags = plMipmap::kCopySrcAlpha;  // Don't care, this is fastest

    Composite( image, x, y, &opts );

    /// HACK for now, since the alpha in the mipmap gets copied straight into the
    /// 32-bit color buffer, but our separate hacked alpha buffer hasn't been updated
    if( fHasAlpha && !respectAlpha )
    {
        HBRUSH brush = ::CreateSolidBrush( RGB( 255, 255, 255 ) );
        RECT rc;
        ::SetRect( &rc, x, y, x + image->GetWidth(), y + image->GetHeight() );
        ::FillRect( fWinAlphaDC, &rc, brush );
        ::DeleteObject( brush );
    }
}

//// DrawClippedImage /////////////////////////////////////////////////////////

void    plDynSurfaceWriter::DrawClippedImage( UInt16 x, UInt16 y, plMipmap *image, 
                                            UInt16 srcClipX, UInt16 srcClipY, 
                                            UInt16 srcClipWidth, UInt16 srcClipHeight, 
                                            hsBool respectAlpha )
{
    if( !IsValid() )
        return;

#if HS_BUILD_FOR_WIN32
    // Flush the GDI first, to make sure it's done
    GdiFlush();
#endif

    plMipmap::CompositeOptions  opts( respectAlpha ? 0 : plMipmap::kForceOpaque );
    if( !fHasAlpha )
        opts.fFlags = plMipmap::kCopySrcAlpha;  // Don't care, this is fastest

    opts.fSrcClipX = srcClipX;
    opts.fSrcClipY = srcClipY;
    opts.fSrcClipWidth = srcClipWidth;
    opts.fSrcClipHeight = srcClipHeight;
    Composite( image, x, y, &opts );

    /// HACK for now, since the alpha in the mipmap gets copied straight into the
    /// 32-bit color buffer, but our separate hacked alpha buffer hasn't been updated
    if( fHasAlpha && !respectAlpha )
    {
        HBRUSH brush = ::CreateSolidBrush( RGB( 255, 255, 255 ) );
        RECT rc;
        ::SetRect( &rc, x, y, x + ( srcClipWidth > 0 ? srcClipWidth : image->GetWidth() ),
                            y + ( srcClipHeight > 0 ? srcClipHeight : image->GetHeight() ) );
        ::FillRect( fWinAlphaDC, &rc, brush );
        ::DeleteObject( brush );
    }
}
*/