#include "Gos.pch"
#include "Text.h"
#include "App.h"
#include "Graphics.h"
#include "File.h"
#include <ddraw.h>
#include <stdio.h>
#include <math.h>

bool (*GosText::Initialize)() = Text::Initialize;
void (*GosText::Shutdown)() = Text::Shutdown;
void (*GosText::Flush)(bool) = Text::Flush;
void (*GosText::Set)( GosText::TextProperty ,... ) = Text::Set;
void (*GosText::Get)( GosText::TextProperty ,... ) = Text::Get;
void (*GosText::Draw)(  _TCHAR *,... ) = Text::Draw;
void (*GosText::DrawUncached)(  _TCHAR *,... ) = Text::DrawUncached;
void (*GosText::StringLength)( DWORD* , DWORD* , const TCHAR*, ... ) = Text::StringBounds;
void (*GosText::DrawBackground)( int , int , int , int , DWORD  ) = Text::DrawBackground;
void (*GosText::SetAttributes)( GosFont3D *, DWORD , float , bool , bool , bool , bool , DWORD ) = Text::SetAttributes;
void (*GosText::SetRegion)( int , int , int , int  ) = Text::SetRegion;
void (*GosText::SelectFont)( GosFont3D * ) = Text::SelectFont;

GosFont3D *	(*GosFont3D::CreateFromFile)( TCHAR *, DWORD, int ) = Font3D::CreateFromFile;
GosFont3D * (*GosFont3D::CreateFromTexture)( GosTexture *, DWORD, int CharCount ) = Font3D::CreateFromTexture;

GosFont3D *		Text::s_pFont = NULL;
DWORD			Text::s_ForegroundColor;
float			Text::s_FontSize;
bool			Text::s_WordWrap;
bool			Text::s_Proportional;
bool			Text::s_Bold;
bool			Text::s_Italic;
DWORD			Text::s_Alignment;
int				Text::s_XPosition;
int				Text::s_YPosition;
int				Text::s_RegionLeft;
int				Text::s_RegionTop;
int				Text::s_RegionRight;
int				Text::s_RegionBottom;
Text *			Text::s_pText = NULL;

Font3D *		Font3D::s_pFont = NULL;
HDC				Font3D::s_hDC = NULL;
DWORD *			Font3D::s_pBMPBits = NULL;
HBITMAP			Font3D::s_hBMP = NULL;
SurfaceCache *	Font3D::s_pGlyphCache = NULL;
SurfaceNode **	Font3D::s_ppGlyphNodes;
int				Font3D::s_iGlyphSpace;
WORD			Font3D::s_PixelLookup[16];


// TODO - REMOVE!! This is just for debugging of the cache manager!!!!!
void RenderTextTextures()
{
	Text::GetTextObj()->RenderTextTextures();
}



void Text::RenderTextTextures()
{
	float width = 128.f;
	float height = 128.f;
	float xStep = 138.f;
	float yStep = 138.f;
	float xPosition;
	float yPosition;
	float top = 10.f;
	float left = 10.f;
	int quadsPerRow = Graphics::Width() / 138;
	SurfaceCache *	pSurf = m_pSurfaceCache;


	SetTextRenderstates();
	Gos3D::SetRenderState( D3DRS_ALPHABLENDENABLE,			FALSE );
	Gos3D::SetRenderState( D3DRS_ALPHATESTENABLE,			FALSE );

	((Font3D*)s_pFont)->RenderGlyphTexture();

	for(int i = 0; i < this->m_iSurfaceCount; i++)
	{
		Graphics::D3DDevice()->SetTexture(0, pSurf->pD3DTexture);
		pSurf = pSurf->pNext;
		
		// Fill vertex buffer
		Font2DVertex* pVertices = (Font2DVertex*)m_pGOSVertextBuffer->Lock();

		float u0 = 0.5f/(float)c_SurfaceWidth;
		float v0 = 0.5f/(float)c_SurfaceHeight;
		float tu = (((float)c_SurfaceWidth)-0.5f)/(float)c_SurfaceWidth;
		float tv = (((float)c_SurfaceHeight)-0.5f)/(float)c_SurfaceHeight;

		xPosition = left+(((i+1)%quadsPerRow)*xStep);
		yPosition = top+(((i+1)/quadsPerRow)*yStep);

		*pVertices++ = Init2DFontVertex( xPosition,			yPosition+height,	0.99f, 1.0f, 0xffffffff, u0,tv);
		*pVertices++ = Init2DFontVertex( xPosition,			yPosition,			0.99f, 1.0f, 0xffffffff, u0,v0);
		*pVertices++ = Init2DFontVertex( xPosition+width,	yPosition,			0.99f, 1.0f, 0xffffffff, tu,v0);
		*pVertices++ = Init2DFontVertex( xPosition+width,	yPosition+height,	0.99f, 1.0f, 0xffffffff, tu,tv);

		// Unlock and render the vertex buffer
		m_pGOSVertextBuffer->Unlock();
		Gos3D::DrawPrimitive( D3DPT_TRIANGLEFAN, 0, 2 );
	}
	ResetTextRenderstates();
}


void Font3D::RenderGlyphTexture()
{
	float width = 128.f;
	float height = 128.f;
	float top = 10.f;
	float left = 10.f;


	if( s_pGlyphCache && s_pGlyphCache->pD3DTexture)
	{
		Graphics::D3DDevice()->SetTexture(0, s_pGlyphCache->pD3DTexture);
		
		// Fill vertex buffer
		Font2DVertex* pVertices = (Font2DVertex*)Text::GetTextObj()->m_pGOSVertextBuffer->Lock();

		float u0 = 0.5f/(float)256;
		float v0 = 0.5f/(float)256;
		float tu = (((float)256)-0.5f)/(float)256;
		float tv = (((float)256)-0.5f)/(float)256;

		*pVertices++ = Text::GetTextObj()->Init2DFontVertex( left,			top+height,	0.99f, 1.0f, 0xffffffff, u0,tv);
		*pVertices++ = Text::GetTextObj()->Init2DFontVertex( left,			top,		0.99f, 1.0f, 0xffffffff, u0,v0);
		*pVertices++ = Text::GetTextObj()->Init2DFontVertex( left+width,	top,		0.99f, 1.0f, 0xffffffff, tu,v0);
		*pVertices++ = Text::GetTextObj()->Init2DFontVertex( left+width,	top+height,	0.99f, 1.0f, 0xffffffff, tu,tv);

		// Unlock and render the vertex buffer
		Text::GetTextObj()->m_pGOSVertextBuffer->Unlock();
		Gos3D::DrawPrimitive( D3DPT_TRIANGLEFAN, 0, 2 );
	}
}


Text::Text()
{
	// Clear everything excpet the vtable pointer
	memset((void*)(((int)this)+4),0,sizeof(*this)-4);
	m_iPointSize = 12;
	m_fScale = 1.f;

}

Text::~Text()
{
}


bool Text::Initialize()
{
	HRESULT rc;
	SurfaceNode *pTree;
	SurfaceNode *pX;
	SurfaceNode *pY;
	
	s_pText = new Text();
	GOS_ASSERT(s_pText);

	//Create heap for text.. Heap::Shutdown automatically frees this
	s_pText->m_pTextHeap = GosHeap::Create(_T("TextHeap"),6400000);
	GOS_ASSERT(s_pText->m_pTextHeap);

	// Initialize the vertex buffer
	s_pText->m_pGOSVertextBuffer = GosVertexBuffer::CreateEmpty(c_VertexBufferSize*sizeof(Font2DVertex), FVF_FONT2DVERTEX, D3DUSAGE_DYNAMIC);

	// Create the first texture...
	SurfaceCache *	pC = new SurfaceCache;

	if( !pC )
	{
		STOP((_T("Unable to create SurfaceCache object")));
		return false;
	}

	memset(pC,0,sizeof(SurfaceCache));

	pTree = s_pText->GetFreeNode();
	pC->pTree = pTree;
	pTree->iWidth = c_SurfaceWidth;
	pTree->iHeight = c_SurfaceHeight;
	pTree->pSurface = pC;
		
	s_pText->m_pSurfaceCache = pC;

	// Preallocate a block for non-cached text
	pX = s_pText->GetFreeNode();
	pY = s_pText->GetFreeNode();
	pTree->pChildX = pX;
	pTree->pChildY = pY;
	pTree->cFull = e_XTreeFull;

	pX->pParent=pTree;
	pX->iWidth = c_SurfaceWidth;
	pX->iHeight = c_SurfaceHeight>>1;
	pX->cFull = e_SubtreesFull;
	pX->pSurface = pC;
	pY->pParent=pTree;
	pY->iWidth = c_SurfaceWidth;
	pY->iHeight = c_SurfaceHeight - pX->iHeight;
	pY->iTop = pX->iHeight;
	pY->pSurface = pC;

	//
	// Need to probe for texture formats??
	//
	rc =  Graphics::D3DDevice()->CreateTexture(c_SurfaceWidth, c_SurfaceHeight, 1, 0, D3DFMT_A4R4G4B4, D3DPOOL_MANAGED, &pC->pD3DTexture );
	if(FAILED(rc))
	{
		STOP((_T("Unable to create SurfaceCache texture")));
		return false;
	}

	s_pText->m_iSurfaceCount++;

	// Total area does NOT include the non-cachable area
	s_pText->m_iTextureAreaFree = c_SurfaceWidth*(c_SurfaceHeight-(pX->iHeight));
	s_pText->m_iTotalTextureArea = s_pText->m_iTextureAreaFree;

	s_XPosition = 0;
	s_YPosition = 0;
	s_RegionLeft = 0;
	s_RegionTop = 0;
	s_RegionRight = 10000;
	s_RegionBottom = 10000;

	return Font3D::Initialize();
}



void Text::Shutdown()
{
	
	if( s_pText )
	{
		SurfaceCache *	current = s_pText->m_pSurfaceCache;

		while(current)
		{
			SurfaceCache *	temp;

			if(current->pD3DTexture)
				current->pD3DTexture->Release();
			
			temp = current;
			current = current->pNext;
			delete temp;
		}
		
		CacheEntry * pEntry = s_pText->m_pMRU;
		while(pEntry)
		{
			if( pEntry->pString)
				s_pText->m_pTextHeap->Free(pEntry->pString);
			if( pEntry->pLines)
				s_pText->m_pTextHeap->Free(pEntry->pLines);

			pEntry = pEntry->pLRU;
		}

		if( s_pText->m_ppTextHashTable )
			free(s_pText->m_ppTextHashTable);

		while(s_pText->m_pNodePool)
		{
			NodePool *pNext = s_pText->m_pNodePool->pNext;
			s_pText->m_pTextHeap->Free(s_pText->m_pNodePool);
			s_pText->m_pNodePool = pNext;
		}

		while(s_pText->m_pCachePool)
		{
			CachePool *pNext = s_pText->m_pCachePool->pNext;
			s_pText->m_pTextHeap->Free(s_pText->m_pCachePool);
			s_pText->m_pCachePool = pNext;
		}

		Font3D::Shutdown();
	
		delete s_pText->m_pGOSVertextBuffer;
		delete s_pText;

		s_pText = (Text*)NULL;
	}
}


void Text::Flush(bool DeleteAll)
{
	s_pText->FlushArea(s_pText->m_iTotalTextureArea, 1);
	
	if(DeleteAll)
	{
		s_pText->Shutdown();
		s_pText->Initialize();
	}

}


void Text::Set( GosText::TextProperty Property,... )
{
	va_list	prop;
	float	oldSize;
	bool	oldFlag;

	va_start(prop, Property);

	switch( Property )
	{
	case Font:
		s_pFont = va_arg(prop, GosFont3D *);
		((Font3D*)s_pFont)->FlushGlyphCache();
		break;
	case ForegroundColor:
		s_ForegroundColor = va_arg(prop, DWORD);
		break;
	case Size:
		oldSize = s_FontSize;
		s_FontSize = (float)va_arg(prop, double);
		if(s_FontSize<0.f)
			s_pText->m_iPointSize = -(int)s_FontSize;
		else
			s_pText->m_fScale=s_FontSize;
		break;
	case WordWrap:
		s_WordWrap = va_arg(prop, bool);
		break;
	case Proportional:
		s_Proportional = va_arg(prop, bool);
		break;
	case Bold:
		oldFlag = s_Bold;
		s_Bold = va_arg(prop, bool);
		if(oldFlag!=s_Bold)
			((Font3D*)s_pFont)->FlushGlyphCache();
		break;
	case Italic:
		oldFlag = s_Italic;
		s_Italic = va_arg(prop, bool);
		if(oldFlag!=s_Italic)
			((Font3D*)s_pFont)->FlushGlyphCache();
		break;
	case Alignment:
		s_Alignment = va_arg(prop, DWORD);
		break;
	case Position:
		s_XPosition = va_arg(prop, int);
		s_YPosition = va_arg(prop, int);
		break;
	case RegionStart:
		s_RegionLeft = va_arg(prop, int);
		s_RegionTop = va_arg(prop, int);
		break;
	case RegionEnd:
		s_RegionRight = va_arg(prop, int);
		s_RegionBottom = va_arg(prop, int);
		break;
	}

	va_end(prop);
}


void Text::Get( GosText::TextProperty Property,... )
{
	va_list		prop;
	GosFont3D	**ppFont;
	int			*piVal;
	DWORD		*pdwVal;
	float		*pfVal;
	bool		*pbVal;

	va_start(prop, Property);

	switch( Property )
	{
	case Font:
		ppFont = va_arg(prop, GosFont3D **);
		*ppFont = s_pFont;
		break;
	case ForegroundColor:
		pdwVal = va_arg(prop, DWORD *);
		*pdwVal = s_ForegroundColor;
		break;
	case Size:
		pfVal = va_arg(prop, float *);
		*pfVal = s_FontSize;
		break;
	case WordWrap:
		pbVal = va_arg(prop, bool *);
		*pbVal = s_WordWrap;
		break;
	case Proportional:
		pbVal = va_arg(prop, bool *);
		*pbVal = s_Proportional;
		break;
	case Bold:
		pbVal = va_arg(prop, bool *);
		*pbVal = s_Bold;
		break;
	case Italic:
		pbVal = va_arg(prop, bool *);
		*pbVal = s_Italic;
		break;
	case Alignment:
		pdwVal = va_arg(prop, DWORD *);
		*pdwVal = s_Alignment;
		break;
	case Position:
		piVal = va_arg(prop, int *);
		*piVal = s_XPosition;
		piVal = va_arg(prop, int *);
		*piVal = s_YPosition;
		break;
	case RegionStart:
		piVal = va_arg(prop, int *);
		*piVal = s_RegionLeft;
		piVal = va_arg(prop, int *);
		*piVal = s_RegionTop;
		break;
	case RegionEnd:
		piVal = va_arg(prop, int *);
		*piVal = s_RegionRight;
		piVal = va_arg(prop, int *);
		*piVal = s_RegionBottom;
		break;
	}

	va_end(prop);
}


void Text::SetAttributes( GosFont3D *pFont, DWORD Foreground, float Size, bool WordWrap, bool Proportional, bool Bold, bool Italic, DWORD Alignment )
{
	if(pFont!=s_pFont||Italic!=Italic||Bold!=Bold)
		((Font3D*)s_pFont)->FlushGlyphCache();
		
	s_pFont = pFont;
	s_ForegroundColor = Foreground;
	s_FontSize=Size;

	if(s_FontSize<0)
		s_pText->m_iPointSize=-(int)s_FontSize;
	else
		s_pText->m_fScale=s_FontSize;
	
	s_WordWrap = WordWrap;
	s_Proportional = Proportional;
	s_Italic = Italic;
	s_Alignment = Alignment;
	s_Bold = Bold;


}


void Text::SetPosition( int PosX, int PosY )
{
		s_XPosition = PosX;
		s_YPosition = PosY;
}


void Text::SetRegion( int Left, int Top, int Right, int Bottom )
{
	GOS_ASSERT( Right>Left && Bottom>Top );
	s_RegionLeft = Left;
	s_RegionTop = Top;
	s_RegionRight = Right;
	s_RegionBottom = Bottom;
}

void Text::SelectFont( GosFont3D *pFont )
{
	s_pFont = pFont;
}



void Text::Draw( _TCHAR *Message,...)
{
	CacheEntry		*text = NULL;
    va_list			args;

	if( !Message || *Message == 0 )
		return;
	
	if(!s_pFont)
	{
		STOP(_T( "No font selected" ));
		return;
	}

    va_start( args, Message );	
	s_pText->m_iStrln = _vsntprintf(s_pText->m_szTextBuffer, sizeof(s_pText->m_szTextBuffer)-1, Message, args);
    va_end( args );

	if( s_pText->m_iStrln < 0 )
	{
		STOP(_T("String too long"));
		return;
	}
	
	if(s_pText->ProcessString())
	{
		if(!(text = s_pText->IsCached(s_pText->m_szTextBuffer)))
			s_pText->AddTextToCache(&text);
	
		if(text)
			s_pText->RenderCachedText(text);
		else
			((Font3D*)s_pFont)->DrawUncachedText(s_pText);
	}

}


void Text::DrawUncached( _TCHAR *Message,...)
{
    va_list			args;

	if( !Message || *Message == 0 )
		return;
	
	if(!s_pFont)
	{
		STOP(_T( "No font selected" ));
		return;
	}

    va_start( args, Message );	
	s_pText->m_iStrln = _vsntprintf(s_pText->m_szTextBuffer, sizeof(s_pText->m_szTextBuffer)-1, Message, args);
    va_end( args );

	if( s_pText->m_iStrln < 0 )
	{
		STOP(_T("String too long"));
		return;
	}
	if(s_pText->ProcessString())
		((Font3D*)s_pFont)->DrawUncachedText(s_pText);

}


bool Text::ProcessString()
{		
	_TCHAR *	pString = m_szTextBuffer;
	Font3D *	pFont = (Font3D*)s_pFont;
	float		regWidth = (float)(s_RegionRight - s_RegionLeft);
	float		regHeight = (float)(s_RegionBottom - s_RegionTop);
	int			count = 0;
	bool		bail = false;
	DWORD		width, height;
	float		yPos, xRef;
	float		scale;
	int			charCount;
	
	switch(s_Alignment&0xf)
	{
	case 0:
		xRef = (float)(s_XPosition);
		if(s_WordWrap)
		{
			if(xRef>=regWidth)
				bail = true;
			else if(xRef<0.f)
				xRef = 0.f;
			else
				regWidth -= xRef;
		}
		break;
	case 1:
		xRef = (float)(s_XPosition+regWidth/2.f);
		if(s_WordWrap)
		{
			if(xRef<=0.f||xRef>=regWidth)
				bail = true;
			else if(s_XPosition > 0)
				regWidth -= 2.f*s_XPosition;
			else
				regWidth += 2.f*s_XPosition;
		}
			break;
	case 2:
		xRef = (float)(s_XPosition+regWidth);
		if(s_WordWrap)
		{
			if(xRef<=0.f)
				bail = true;
			else if(xRef>regWidth)
				xRef=regWidth;
			else if(s_XPosition > 0)
				regWidth -= s_XPosition;
			else
				regWidth += s_XPosition;
		}
			break;
	default:
		STOP(_T("Invalid horizontal alignment" ));
		break;
	}

	if(regWidth<=0||regHeight<=0||bail)
		return false;
	
	pFont->UpdateFont();
	scale = pFont->Scale();
	yPos = (float)s_YPosition;
	height = pFont->Height();

	// Process entire string for correct line breaks
	// (or until we run out of slots...)
	while(*pString && count < 512)
	{
		if(!s_WordWrap)
		{
			DWORD		tmp;
			charCount = pFont->LineBounds(&width,&tmp,pString);
		}
		else
		{
			_TCHAR *	pFrag = pString;
			bool		first = true;
			DWORD		fragWidth = 0;
			
			width = 0;
			charCount = 0;

			// Process the string into fragments that
			// have linebreaks within the bounds
			while(true)
			{
				int count = pFont->WordWidth(&fragWidth, pFrag);
				float currentWidth = (float)ceil((width+fragWidth+pFont->SpaceWidth())*scale);
				_TCHAR * pEnd = pFrag + count;

				if( currentWidth>regWidth)
				{
					if(first)
					{
						width = fragWidth;
						if(*pEnd==' ')
						{
							*pEnd='\n';
							count++;
						}
						charCount = count;
					}
					// Are we about to split the line by inserting a
					// newline into a space.
					else if(pFrag[-1]==' ')
					{
						pFrag[-1]='\n';
						width -= pFont->SpaceWidth();
					}


					break;
				}
				else if(!*pEnd || *pEnd=='\n' || *pEnd==0x0a || *pEnd==0x0d)
				{
					width += fragWidth;
					charCount+=count;

					break;
				}


				first = false;
				width += fragWidth+pFont->SpaceWidth();
				count++;
				charCount += count;
				pFrag+=count;

			}
		}
			
		if(pString[charCount]=='\n'||pString[charCount]==0x0a||pString[charCount]==0x0d)
			charCount++;

		m_pLOT[count].pStart = pString;
		m_pLOT[count].iCount = charCount;
		m_pLOT[count].iWidth = width;
		m_pLOT[count].iHeight = height;
		m_pLOT[count].fYPos = yPos + (float)s_RegionTop;


		pString+=charCount;
		count++;
		yPos += height * scale;
	}


	// The string has now been correctly formatted to wrap within the
	// bounds and the results are in the m_pLOT buckets. Now we'll
	// process these buckets so that we only cache the visible part
	// of the string

	int		firstLine;
	float	yTop;
	float	textHeight;

	
	textHeight = count*height*scale;

	switch((s_Alignment&0xf0000)>>16)
	{
	case 0: // String is 'top' justified
		yTop = (float)s_YPosition;
		break;
	case 1: // String is centered vertically
		yTop = (float)(s_YPosition+((regHeight-textHeight)/2.f));
		break;
	case 2: // String is 'bottom' justified
		yTop = (float)(s_YPosition+regHeight-textHeight);
		break;	
	default:
		STOP(_T("Invalid vertical alignment" ));
		break;
	}

	if(yTop<0.f)
	{
		firstLine = (int)(-yTop / (height * scale));
		yTop += firstLine*height*scale;
	}
	else
		firstLine = 0;


	// The string may have been clipped/wraped/moved,
	// so fix up references etc. if needed and calculate
	// the xPositions of the lines
	int offset = m_pLOT[firstLine].pStart-m_szTextBuffer;
	int length = 0;
	int i;

	for( i = firstLine; i < count && yTop < regHeight; i++)
	{	
		m_pLOT[i].pStart -= offset;
		m_pLOT[i].fYPos = yTop + (float)s_RegionTop;
		length+=m_pLOT[i].iCount;
		yTop += height * scale;

		switch(s_Alignment&0xf)
		{
		case 0:
			m_pLOT[i].fXPos = s_RegionLeft + xRef;
			break;
		case 1:
			m_pLOT[i].fXPos = s_RegionLeft + xRef-m_pLOT[i].iWidth*scale/2.f;
			break;
		case 2:
			m_pLOT[i].fXPos = s_RegionLeft + xRef-m_pLOT[i].iWidth*scale;
			break;
		default:
			STOP(_T("Invalid horizontal alignment" ));
		}
	}
	
	if(offset)
	{
		memmove(m_szTextBuffer,m_pLOT[firstLine].pStart+offset,length*sizeof(_TCHAR));
		memmove(m_pLOT,&m_pLOT[firstLine],(i-firstLine)*sizeof(LineOfText));
	}
	
	m_szTextBuffer[length] = 0;
	m_iStrln = length;
	m_pLOT[i].pStart = 0;

	if(length>0)
		return true;
	return false;
}



void Text::RebalanceTextHashTable()
{
	if( !m_ppTextHashTable )
	{
		// creating very first gosobj - start with a reasonable hash table mark all as in use
		m_dwTextHashEntriesInUse = c_InitialTextHashTableSize;
		m_dwTextHashCutoff = c_InitialTextHashTableSize;
		m_dwTextHashMask = c_InitialTextHashTableSize-1;
		m_ppTextHashTable = (CacheEntry **) malloc( m_dwTextHashEntriesInUse * sizeof(CacheEntry *) );
		GOS_ASSERT(m_ppTextHashTable);
		m_dwTextHashSpaceAvailable += c_MaxAverageTextBucketSize * m_dwTextHashEntriesInUse; // no need to rebalance until all of these are used
		for( unsigned long i = 0; i< c_InitialTextHashTableSize; i++ )
			m_ppTextHashTable[i] = NULL;
	}
	else
	{
		m_dwTextHashSpaceAvailable += c_MaxAverageTextBucketSize;	// we're opening up *one* new hash bucket

		if( ! (m_dwTextHashEntriesInUse & ( m_dwTextHashEntriesInUse - 1 ) ) )
		{
			// we've hit a power of two - double the has table before using one more entry
			m_dwTextHashCutoff = m_dwTextHashEntriesInUse;
			m_dwTextHashMask = ( m_dwTextHashMask << 1 ) + 1;
			m_ppTextHashTable = (CacheEntry **) realloc( (void *)m_ppTextHashTable, m_dwTextHashEntriesInUse * 2 * sizeof(CacheEntry *) );
			GOS_ASSERT(m_ppTextHashTable);
			for( unsigned long i = m_dwTextHashEntriesInUse; i< m_dwTextHashEntriesInUse*2; i++ )
				m_ppTextHashTable[i] = NULL;

		}
		// now, we split the entries from the first double-sized bucket into the newly in-use bucket
		unsigned long newBucket = m_dwTextHashEntriesInUse++;
		unsigned long oldBucket = newBucket - m_dwTextHashCutoff;

		CacheEntry *pObj;
		CacheEntry *pNewLowChain = NULL;
		CacheEntry *pNewLowEnd = NULL;
		CacheEntry *pNewHighChain = NULL;
		CacheEntry *pNewHighEnd = NULL;

		for( pObj = m_ppTextHashTable[oldBucket]; pObj; pObj = pObj->pNextHash )
		{
			if( ( pObj->dwHashID & m_dwTextHashMask ) == oldBucket )
			{
				// append to new version of low chain:
				if( !pNewLowChain )
					pNewLowChain = pObj;
				else
					pNewLowEnd->pNextHash = pObj;
				pNewLowEnd = pObj;
			}
			else
			{
				// append to high chain:
				if( !pNewHighChain )
					pNewHighChain = pObj;
				else
					pNewHighEnd->pNextHash = pObj;
				pNewHighEnd = pObj;
			}
		}
		if( pNewLowEnd )
			pNewLowEnd->pNextHash = NULL;
		if( pNewHighEnd )
			pNewHighEnd->pNextHash = NULL;
		m_ppTextHashTable[oldBucket] = pNewLowChain;
		m_ppTextHashTable[newBucket] = pNewHighChain;
	}
}


DWORD Text::HashID( _TCHAR *pString )
{
	DWORD id = 0;
	DWORD shift = 0;
	while(*pString)
	{
		id+=((unsigned long)*pString++)<<(shift++&0x3);
	}
	return id;
}


DWORD Text::HashIndex( DWORD HashID )
{
	HashID &= m_dwTextHashMask;
	if( HashID >= m_dwTextHashEntriesInUse )
		HashID -= m_dwTextHashCutoff;
	return HashID;
}


CacheEntry* Text::IsCached(_TCHAR *pString)
{
	if(!m_ppTextHashTable)
		return NULL;
		
	CacheEntry * pEntry = m_ppTextHashTable[HashIndex(HashID(pString))];
	int flags = ((s_Proportional&0x1))|((s_Bold&0x1)<<1)|((s_Italic&0x1)<<2|((s_WordWrap&0x1)<<3));

	while(pEntry)
	{
		if(!_tcscmp(pEntry->pString,pString))
		{
			if(pEntry->pFont==s_pFont&&pEntry->iPointSize==m_iPointSize&&pEntry->fScale==m_fScale&&pEntry->iFlags==flags&&pEntry->dwAlignment==s_Alignment)
			{
				if(pEntry->iXPos!=(s_XPosition+s_RegionLeft) || pEntry->iYPos!=(s_YPosition+s_RegionTop))
				{

					// The string is cached, but the screen position values have changed.
					// None of the line-break, clip or wrap information, scale or point size
					// have changed, so by recalculating the position values here, the cached
					// string can be used again!!

					float xDiff = (float)(s_XPosition + s_RegionLeft - pEntry->iXPos);
					float yDiff = (float)(s_YPosition + s_RegionTop - pEntry->iYPos); 

					pEntry->iXPos=s_XPosition+s_RegionLeft;
					pEntry->iYPos=s_YPosition+s_RegionTop;

					for(int i = 0; i<pEntry->iLineCount; i++)
					{
						pEntry->pLines[i].fXPos+=xDiff;
						pEntry->pLines[i].fYPos+=yDiff;
					}
				}
				
				break;
			}
		}
		pEntry = pEntry->pNextHash;
	}

	return(pEntry);
}


void Text::AddTextToCache(CacheEntry **ppText)
{
	int				freeWidth;
	CacheEntry *	newEntry;
	bool			areaOK;
	int				width = 0;
	int				count = 0;
	bool			addOK = false;
	Font3D *		pFont = (Font3D*)s_pFont;
	
	*ppText = (CacheEntry*)NULL;
	newEntry = GetFreeEntry();

	newEntry->iXPos = s_XPosition+s_RegionLeft;
	newEntry->iYPos = s_YPosition+s_RegionTop;
	newEntry->fScale = m_fScale;
	newEntry->iPointSize = m_iPointSize;
	newEntry->dwAlignment = s_Alignment;

	newEntry->pString = (_TCHAR*)m_pTextHeap->Malloc(sizeof(_TCHAR)*(m_iStrln+1));

	if(newEntry->pString)
	{
		while(m_pLOT[count].pStart)
		{
			newEntry->iLineCount++;
			width+=m_pLOT[count++].iWidth;
		}

		freeWidth = GetTextureSpace(pFont->Height());

		if(freeWidth>width)
			areaOK = true;
		else
		{
			switch(AddTextureArea(width,pFont->Height()))
			{
			case e_SpaceOK:		// Enough space to cache and render
				areaOK = true;
				break;
			case e_OutOfSpace:	// Not enough space
				areaOK = false;
			}
		}

		newEntry->pLines = (CachedLine*)m_pTextHeap->Malloc(sizeof(CachedLine)*newEntry->iLineCount);

		if(newEntry->pLines&&areaOK)
		{
			memset(newEntry->pLines,0,sizeof(CachedLine)*newEntry->iLineCount);
			_tcscpy(newEntry->pString,m_szTextBuffer);
			newEntry->iFlags = ((s_Proportional&0x1))|((s_Bold&0x1)<<1)|((s_Italic&0x1)<<2)|((s_WordWrap&0x1)<<3);
			newEntry->pFont = pFont;
				
			if( !m_dwTextHashSpaceAvailable-- )
				RebalanceTextHashTable();

			newEntry->dwHashID = HashID(newEntry->pString);
			DWORD hashIndex = HashIndex(newEntry->dwHashID);

			newEntry->pNextHash = m_ppTextHashTable[hashIndex];
			m_ppTextHashTable[hashIndex] = newEntry;

			*ppText = newEntry;

			pFont->DrawStringToTexture(s_pText,newEntry);

			addOK = true;
		}
	}

	if(!addOK&&newEntry)
	{
		if( newEntry->pString)
			m_pTextHeap->Free(newEntry->pString);
		if( newEntry->pLines)
			m_pTextHeap->Free(newEntry->pLines);
		FreeEntry(newEntry);
	}

}


int Text::AddTextureArea(int Width, int Height)
{

	int freeWidth = GetTextureSpace(Height);
	SurfaceCache *	last = m_pSurfaceCache;

	while(last->pNext)
		last=last->pNext;

	while(freeWidth<Width&&(c_MaxSurfaces-m_iSurfaceCount)>0)
	{
		HRESULT rc; 
		SurfaceCache *	pC = new SurfaceCache;
		if(pC)
		{
			memset(pC,0,sizeof(SurfaceCache));

			rc =  Graphics::D3DDevice()->CreateTexture(c_SurfaceWidth, c_SurfaceHeight, 1, 0, D3DFMT_A4R4G4B4, D3DPOOL_MANAGED, &pC->pD3DTexture );
			if(FAILED(rc))
			{
				delete pC;
				break;
			}

			pC->pTree = GetFreeNode();
			pC->pTree->iWidth = c_SurfaceWidth;
			pC->pTree->iHeight = c_SurfaceHeight;
			pC->pTree->pSurface = pC;
	
			m_iTextureAreaFree += c_SurfaceWidth*c_SurfaceHeight;
			m_iTotalTextureArea += c_SurfaceWidth*c_SurfaceHeight;
			freeWidth+=c_SurfaceWidth*(c_SurfaceHeight/Height);
			m_iSurfaceCount++;
			last->pNext = pC;
			last = pC;
		}
	}

	if(freeWidth>Width)
		return e_SpaceOK;

	// Still not enough space so flush all text that is
	// more than 3 frames old to ry to free up more space
	if(FlushArea(Width,Height,3))
		return e_SpaceOK;

	return e_OutOfSpace;
}


int Text::GetTextureSpace(int Height)
{
	int	space = 0;

	SurfaceCache *	pC = m_pSurfaceCache;
	
	while(pC)
	{
		space += GetNodeSpace(pC->pTree,Height);
		pC=pC->pNext;
	}
	return space;
}


int Text::GetNodeSpace(SurfaceNode *pNode, int Height)
{
	int	space = 0;

	if(pNode&&pNode->cFull!=e_SubtreesFull)
	{
		if( pNode->pChildX&&pNode->pChildY)
		{
			space += GetNodeSpace(pNode->pChildX, Height);
			space += GetNodeSpace(pNode->pChildY, Height);
		}
		else
		{
			space += (pNode->iHeight/Height) * pNode->iWidth;
		}
	}
	return space;
}


bool Text::FlushArea(int Width, int Height, int Age)
{
	int freeWidth = GetTextureSpace(Height);
	int FrameNo = App::s_frameNo;

	while(Width>freeWidth && m_pLRU && (App::s_frameNo - m_pLRU->iFrameUsed) >= Age)
	{
		for(int i = 0; i<m_pLRU->iLineCount; i++)
		{
			CachedLine *	pLine = &m_pLRU->pLines[i];
			for(int j=0;pLine->pPolyNodes[j];j++)
			{
				SurfaceNode * pNodeChild = pLine->pPolyNodes[j];
				SurfaceNode * pNodeParent = pNodeChild->pParent;
				SurfaceCache * pSurface = pNodeChild->pSurface;

				m_iTextureAreaFree+=pNodeChild->iHeight*pNodeChild->iWidth;

				pNodeChild->cFull = e_XYNotFull;
				
				while(pNodeParent)
				{
					
					SurfaceNode * pChildX = pNodeParent->pChildX;
					SurfaceNode * pChildY = pNodeParent->pChildY;
									
					if(!pChildX->cFull&&!pChildY->cFull&&!pChildX->pChildX&&!pChildX->pChildY&&!pChildY->pChildX&&!pChildY->pChildY)
					{
						FreeNode(pNodeParent->pChildX);
						pNodeParent->pChildX = (SurfaceNode*)NULL;
						FreeNode(pNodeParent->pChildY);
						pNodeParent->pChildY= (SurfaceNode*)NULL;
						pNodeParent->cFull = e_XYNotFull;
					}
					else
					{
						if(pNodeParent->pChildX==pNodeChild)
						{
							// If the parent doesn't think that this subtree is
							// full, then terminate early..
							if(!(pNodeParent->cFull&e_XTreeFull))
								break;
							pNodeParent->cFull&=~e_XTreeFull;
						}
						else if(pNodeParent->pChildY==pNodeChild)
						{
							// If the parent doesn't think that this subtree is
							// full, then terminate early..
							if(!(pNodeParent->cFull&e_YTreeFull))
								break;
							pNodeParent->cFull&=~e_YTreeFull;
						}
						else
							STOP(_T( "Parent and child unrelated?" ));
					}

					pNodeChild=pNodeParent;
					pNodeParent=pNodeParent->pParent;
				}
			}
		}
		
		freeWidth = GetTextureSpace(Height);

		if( m_pLRU->pString)
			m_pTextHeap->Free(m_pLRU->pString);
		if( m_pLRU->pLines)
			m_pTextHeap->Free(m_pLRU->pLines);

		// Remove CacheEntry from hash table
		unsigned long hashIndex = HashIndex(m_pLRU->dwHashID);

		if( m_ppTextHashTable[hashIndex] == m_pLRU )
		{
			m_ppTextHashTable[hashIndex] = m_pLRU->pNextHash;
		}
		else
		{
			CacheEntry *pPrev = m_ppTextHashTable[hashIndex];
			while( pPrev->pNextHash != m_pLRU )
			{
				GOS_ASSERT( pPrev->pNextHash );
				pPrev = pPrev->pNextHash;
			}
			pPrev->pNextHash = m_pLRU->pNextHash;
		}

		m_dwTextHashSpaceAvailable++;

		// Put CacheEntry back into free list
		FreeEntry(m_pLRU);

		// Get next LRU entry
		m_pLRU = m_pLRU->pMRU;
		if(m_pLRU)
			m_pLRU->pLRU = NULL;
		else
			m_pMRU = NULL;
	}

	if( freeWidth >= Width )
		return true;
	return false;
}
 

SurfaceNode * Text::GetFreeNode()
{
	SurfaceNode * pNode = NULL;

	if(m_pFreeNode)
	{
		pNode = m_pFreeNode;
		m_pFreeNode = pNode->pParent;
	}
	else
	{
		NodePool *pool;

		if(!m_pNodePool)
		{
			// Allocate a first block of entries;
			pool = m_pNodePool = (NodePool*)m_pTextHeap->Malloc(sizeof(NodePool));
			GOS_ASSERT(pool);
		}
		else
		{
			pool = m_pNodePool;

			while(pool->pNext)
				pool=pool->pNext;

			pool->pNext = (NodePool*)m_pTextHeap->Malloc(sizeof(NodePool));
			GOS_ASSERT(pool->pNext);
			pool=pool->pNext;
		}

		memset(pool,0,sizeof(NodePool));

		// Chain free nodes using parent pointers 
		for( int i = 1; i < c_NodePoolSize-1; i++)
			pool->Pool[i].pParent = &pool->Pool[i+1];

		pNode = &pool->Pool[0];
		m_pFreeNode = &pool->Pool[1];
	}

	memset(pNode,0,sizeof(SurfaceNode));
	return pNode;
}


void Text::FreeNode(SurfaceNode *pNode)
{
	pNode->pParent=m_pFreeNode;
	m_pFreeNode = pNode;
}


CacheEntry * Text::GetFreeEntry()
{
	CacheEntry * pEntry = NULL;

	if(m_pFreeCache)
	{
		pEntry = m_pFreeCache;
		m_pFreeCache = pEntry->pLRU;
	}
	else
	{
		CachePool *pool;

		if(!m_pCachePool)
		{
			// Allocate a first block of entries;
			pool = m_pCachePool = (CachePool*)m_pTextHeap->Malloc(sizeof(CachePool));
			GOS_ASSERT(pool);
		}
		else
		{
			pool = m_pCachePool;

			while(pool->pNext)
				pool=pool->pNext;

			pool->pNext = (CachePool*)m_pTextHeap->Malloc(sizeof(CachePool));
			GOS_ASSERT(pool->pNext);
			pool=pool->pNext;
		}

		memset(pool,0,sizeof(CachePool));

		// Chain free nodes using LRU pointers 
		for( int i = 1; i < c_CachePoolSize-1; i++)
			pool->Pool[i].pLRU = &pool->Pool[i+1];

		pEntry = &pool->Pool[0];
		m_pFreeCache = &pool->Pool[1];
	}

	memset(pEntry,0,sizeof(CacheEntry));
	return pEntry;
}


void Text::FreeEntry(CacheEntry *pEntry)
{
	pEntry->pLRU=m_pFreeCache;
	m_pFreeCache = pEntry;
}


SurfaceNode * Text::FindFreeNode(CacheEntry *pEntry, int Width, int Height)
{
	SurfaceCache *	pSurface;
	SurfaceNode *	pNode;

	pNode = NULL;
	pSurface = m_pSurfaceCache;
	
	while(!pNode&&pSurface)
	{
		pNode=SearchTree(pSurface->pTree, Width, Height);
		
		if(pNode)
			pNode->pSurface = pSurface;
		else
			pSurface=pSurface->pNext;
	}
	
	GOS_ASSERT(pNode)

	m_iTextureAreaFree-=pNode->iHeight*pNode->iWidth;

	return pNode;
}


SurfaceNode * Text::SearchTree( SurfaceNode * pTree, int Width, int Height, bool NoSplit)
{
	SurfaceNode *	pNode = (SurfaceNode*)NULL;

	if(pTree&&pTree->cFull!=e_SubtreesFull)
	{
		if(!(pNode = SearchTree(pTree->pChildX, Width, Height,NoSplit)))
		{
			if(!(pNode = SearchTree(pTree->pChildY, Width, Height,NoSplit)))
			{
				if(!pTree->pChildX&&!pTree->pChildY)
				{
					if(pTree->iHeight>Height&&(NoSplit?pTree->iWidth>=Width:1))
					{
						pNode = SplitNodeHorizontal(pTree,Width,Height);
					}
					else if(pTree->iHeight==Height&&(NoSplit?pTree->iWidth>=Width:1))
					{
						pNode = SplitNodeVertical(pTree,Width);
					}
				}
			}
		}
	}
	return pNode;
}


SurfaceNode * Text::SplitNodeVertical( SurfaceNode * pTree, int Width)
{
	SurfaceNode * pNode = (SurfaceNode*)NULL;

	if(Width>=pTree->iWidth)
	{
		pNode=pTree;
		pNode->cFull=e_SubtreesFull;

		SurfaceNode * pNodeParent = pNode->pParent;
		SurfaceNode * pNodeChild = pNode;
		while(pNodeParent)
		{
			if(pNodeParent->pChildX==pNodeChild)
				pNodeParent->cFull|=e_XTreeFull;
			else if(pNodeParent->pChildY==pNodeChild)
				pNodeParent->cFull|=e_YTreeFull;
			else
				STOP(_T( "Parent and child unrelated?" ));
			
			if(pNodeParent->cFull==e_SubtreesFull)
			{
				pNodeChild=pNodeParent;
				pNodeParent=pNodeParent->pParent;
			}
			else
				pNodeParent=(SurfaceNode*)NULL;
		}
	}

	else
	{
		SurfaceNode * pChildX = GetFreeNode();
		SurfaceNode * pChildY = GetFreeNode();

		pChildX->pParent=pTree;
		pChildX->cFull=e_SubtreesFull;
		pChildX->iTop=pTree->iTop;
		pChildX->iLeft=pTree->iLeft;
		pChildX->iWidth=Width;
		pChildX->iHeight=pTree->iHeight;

		pChildY->pParent=pTree;
		pChildY->cFull=e_XYNotFull;
		pChildY->iTop=pTree->iTop;
		pChildY->iLeft=pTree->iLeft+Width;
		pChildY->iWidth=pTree->iWidth-Width;
		pChildY->iHeight=pTree->iHeight;

		pTree->pChildX=pChildX;
		pTree->pChildY=pChildY;
		pTree->cFull=e_XTreeFull;
		pNode=pChildX;
		
	}

	return pNode;
}


SurfaceNode * Text::SplitNodeHorizontal( SurfaceNode * pTree, int Width, int Height)
{
	SurfaceNode * pNode;

	SurfaceNode * pChildTop = GetFreeNode();
	SurfaceNode * pChildBot = GetFreeNode();

	pChildTop->pParent=pTree;
	pChildTop->cFull=e_XYNotFull;
	pChildTop->iTop=pTree->iTop;
	pChildTop->iLeft=pTree->iLeft;
	pChildTop->iWidth=pTree->iWidth;
	pChildTop->iHeight=Height;

	pChildBot->pParent=pTree;
	pChildBot->cFull=e_XYNotFull;
	pChildBot->iTop=pTree->iTop+Height;
	pChildBot->iLeft=pTree->iLeft;
	pChildBot->iWidth=pTree->iWidth;
	pChildBot->iHeight=pTree->iHeight-Height;

	pTree->pChildX=pChildTop;
	pTree->pChildY=pChildBot;

	pNode = SplitNodeVertical(pChildTop,Width);

	return pNode;
}


void Text::SetDefaultTexture()
{
	Graphics::D3DDevice()->SetTexture(0, m_pSurfaceCache[0].pD3DTexture );
}


SurfaceCache * Text::LockSurface(int TexID, D3DLOCKED_RECT *pD3Dlr)
{
	int				count = 0;
	SurfaceCache	*pSC = m_pSurfaceCache; 
	HRESULT			rc;

	while(count!=TexID&&pSC)
	{
		TexID--;
		pSC = pSC->pNext;
	}

	GOS_ASSERT(pSC);
	
	rc = pSC->pD3DTexture->LockRect(0,pD3Dlr,0,0);
	if(FAILED(rc)) STOP((_T("Unable to lock texture")));

	return pSC;
}

void Text::UnlockSurface(SurfaceCache *pSC)
{
	HRESULT rc;
	GOS_ASSERT(pSC)
	rc = pSC->pD3DTexture->UnlockRect(0);
	if(FAILED(rc)) STOP((_T("Unable to unlock texture")));
}

void Text::SetTextRenderstates()
{
	int i = 0;

	Gos3D::GetRenderState( D3DRS_CULLMODE,					&m_dwSavedStates[i++]);
	Gos3D::GetRenderState( D3DRS_ALPHABLENDENABLE,			&m_dwSavedStates[i++]);
	Gos3D::GetRenderState( D3DRS_SRCBLEND,					&m_dwSavedStates[i++]);
	Gos3D::GetRenderState( D3DRS_DESTBLEND,					&m_dwSavedStates[i++]);
	Gos3D::GetRenderState( D3DRS_ALPHATESTENABLE,			&m_dwSavedStates[i++]);
	Gos3D::GetRenderState( D3DRS_ALPHAREF,					&m_dwSavedStates[i++]);
	Gos3D::GetRenderState( D3DRS_ALPHAFUNC,					&m_dwSavedStates[i++]);
	Gos3D::GetRenderState( D3DRS_FILLMODE,					&m_dwSavedStates[i++]);
	Gos3D::GetRenderState( D3DRS_ZENABLE,					&m_dwSavedStates[i++]);
	Gos3D::GetRenderState( D3DRS_STENCILENABLE,				&m_dwSavedStates[i++]);
	Gos3D::GetRenderState( D3DRS_CLIPPING,					&m_dwSavedStates[i++]);
	Gos3D::GetRenderState( D3DRS_EDGEANTIALIAS,				&m_dwSavedStates[i++]);
	Gos3D::GetRenderState( D3DRS_CLIPPLANEENABLE,			&m_dwSavedStates[i++]);
	Gos3D::GetRenderState( D3DRS_VERTEXBLEND,				&m_dwSavedStates[i++]);
	Gos3D::GetRenderState( D3DRS_INDEXEDVERTEXBLENDENABLE,	&m_dwSavedStates[i++]);
	Gos3D::GetRenderState( D3DRS_FOGENABLE,					&m_dwSavedStates[i++]);

	Gos3D::GetTextureStageState( 0, D3DTSS_COLOROP,			&m_dwSavedStates[i++]);
	Gos3D::GetTextureStageState( 0, D3DTSS_COLORARG1,		&m_dwSavedStates[i++]);
	Gos3D::GetTextureStageState( 0, D3DTSS_COLORARG2,		&m_dwSavedStates[i++]);
	Gos3D::GetTextureStageState( 0, D3DTSS_ALPHAOP,			&m_dwSavedStates[i++]);
	Gos3D::GetTextureStageState( 0, D3DTSS_ALPHAARG1,		&m_dwSavedStates[i++]);
	Gos3D::GetTextureStageState( 0, D3DTSS_ALPHAARG2,		&m_dwSavedStates[i++]);
	Gos3D::GetTextureStageState( 0, D3DTSS_MINFILTER,		&m_dwSavedStates[i++]);
	Gos3D::GetTextureStageState( 0, D3DTSS_MAGFILTER,		&m_dwSavedStates[i++]);
	Gos3D::GetTextureStageState( 0, D3DTSS_MIPFILTER,		&m_dwSavedStates[i++]);
	Gos3D::GetTextureStageState( 0, D3DTSS_TEXCOORDINDEX,	&m_dwSavedStates[i++]);
	Gos3D::GetTextureStageState( 0, D3DTSS_TEXTURETRANSFORMFLAGS,	&m_dwSavedStates[i++]);
	Gos3D::GetTextureStageState( 1, D3DTSS_COLOROP,			&m_dwSavedStates[i++]);
	Gos3D::GetTextureStageState( 1, D3DTSS_ALPHAOP,			&m_dwSavedStates[i++]);

	Gos3D::GetVertexShader(&m_pSavedVertexShader);
	Gos3D::GetStreamSource(0,&m_pSavedVertexBuffer,&m_uiSavedStride);

	Gos3D::SetRenderState( D3DRS_CULLMODE,					D3DCULL_NONE);
	Gos3D::SetRenderState( D3DRS_ALPHABLENDENABLE,			TRUE );
	Gos3D::SetRenderState( D3DRS_SRCBLEND,					D3DBLEND_SRCALPHA );
	Gos3D::SetRenderState( D3DRS_DESTBLEND,					D3DBLEND_INVSRCALPHA );
	Gos3D::SetRenderState( D3DRS_ALPHATESTENABLE,			TRUE );
	Gos3D::SetRenderState( D3DRS_ALPHAREF,					0x08 );
	Gos3D::SetRenderState( D3DRS_ALPHAFUNC,					D3DCMP_GREATEREQUAL );
	Gos3D::SetRenderState( D3DRS_FILLMODE,					D3DFILL_SOLID );
	Gos3D::SetRenderState( D3DRS_ZENABLE,					FALSE );
	Gos3D::SetRenderState( D3DRS_STENCILENABLE,				FALSE );
	Gos3D::SetRenderState( D3DRS_CLIPPING,					TRUE );
	Gos3D::SetRenderState( D3DRS_EDGEANTIALIAS,				FALSE );
	Gos3D::SetRenderState( D3DRS_CLIPPLANEENABLE,			FALSE );
	Gos3D::SetRenderState( D3DRS_VERTEXBLEND,				FALSE );
	Gos3D::SetRenderState( D3DRS_INDEXEDVERTEXBLENDENABLE,	FALSE );
	Gos3D::SetRenderState( D3DRS_FOGENABLE,					FALSE );

	Gos3D::SetTextureStageState( 0, D3DTSS_COLOROP,			D3DTOP_MODULATE );
	Gos3D::SetTextureStageState( 0, D3DTSS_COLORARG1,		D3DTA_TEXTURE );
	Gos3D::SetTextureStageState( 0, D3DTSS_COLORARG2,		D3DTA_DIFFUSE );
	Gos3D::SetTextureStageState( 0, D3DTSS_ALPHAOP,			D3DTOP_MODULATE );
	Gos3D::SetTextureStageState( 0, D3DTSS_ALPHAARG1,		D3DTA_TEXTURE );
	Gos3D::SetTextureStageState( 0, D3DTSS_ALPHAARG2,		D3DTA_DIFFUSE );
	Gos3D::SetTextureStageState( 0, D3DTSS_MINFILTER,		D3DTEXF_POINT );
	Gos3D::SetTextureStageState( 0, D3DTSS_MAGFILTER,		D3DTEXF_POINT );
	Gos3D::SetTextureStageState( 0, D3DTSS_MIPFILTER,		D3DTEXF_NONE );
	Gos3D::SetTextureStageState( 0, D3DTSS_TEXCOORDINDEX,	0 );
	Gos3D::SetTextureStageState( 0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE );
	Gos3D::SetTextureStageState( 1, D3DTSS_COLOROP,			D3DTOP_DISABLE );
	Gos3D::SetTextureStageState( 1, D3DTSS_ALPHAOP,			D3DTOP_DISABLE );

	Gos3D::SetFVFShader( FVF_FONT2DVERTEX );
	Gos3D::SetStreamSource( 0, m_pGOSVertextBuffer, sizeof(Font2DVertex) );

	m_bResetStates = true;
}


void Text::ResetTextRenderstates()
{
	if(m_bResetStates)
	{
		int i = 0;

		Gos3D::SetRenderState( D3DRS_CULLMODE,					m_dwSavedStates[i++]);
		Gos3D::SetRenderState( D3DRS_ALPHABLENDENABLE,			m_dwSavedStates[i++]);
		Gos3D::SetRenderState( D3DRS_SRCBLEND,					m_dwSavedStates[i++]);
		Gos3D::SetRenderState( D3DRS_DESTBLEND,					m_dwSavedStates[i++]);
		Gos3D::SetRenderState( D3DRS_ALPHATESTENABLE,			m_dwSavedStates[i++]);
		Gos3D::SetRenderState( D3DRS_ALPHAREF,					m_dwSavedStates[i++]);
		Gos3D::SetRenderState( D3DRS_ALPHAFUNC,					m_dwSavedStates[i++]);
		Gos3D::SetRenderState( D3DRS_FILLMODE,					m_dwSavedStates[i++]);
		Gos3D::SetRenderState( D3DRS_ZENABLE,					m_dwSavedStates[i++]);
		Gos3D::SetRenderState( D3DRS_STENCILENABLE,				m_dwSavedStates[i++]);
		Gos3D::SetRenderState( D3DRS_CLIPPING,					m_dwSavedStates[i++]);
		Gos3D::SetRenderState( D3DRS_EDGEANTIALIAS,				m_dwSavedStates[i++]);
		Gos3D::SetRenderState( D3DRS_CLIPPLANEENABLE,			m_dwSavedStates[i++]);
		Gos3D::SetRenderState( D3DRS_VERTEXBLEND,				m_dwSavedStates[i++]);
		Gos3D::SetRenderState( D3DRS_INDEXEDVERTEXBLENDENABLE,	m_dwSavedStates[i++]);
		Gos3D::SetRenderState( D3DRS_FOGENABLE,					m_dwSavedStates[i++]);
		Gos3D::SetTextureStageState( 0, D3DTSS_COLOROP,			m_dwSavedStates[i++]);
		Gos3D::SetTextureStageState( 0, D3DTSS_COLORARG1,		m_dwSavedStates[i++]);
		Gos3D::SetTextureStageState( 0, D3DTSS_COLORARG2,		m_dwSavedStates[i++]);
		Gos3D::SetTextureStageState( 0, D3DTSS_ALPHAOP,			m_dwSavedStates[i++]);
		Gos3D::SetTextureStageState( 0, D3DTSS_ALPHAARG1,		m_dwSavedStates[i++]);
		Gos3D::SetTextureStageState( 0, D3DTSS_ALPHAARG2,		m_dwSavedStates[i++]);
		Gos3D::SetTextureStageState( 0, D3DTSS_MINFILTER,		m_dwSavedStates[i++]);
		Gos3D::SetTextureStageState( 0, D3DTSS_MAGFILTER,		m_dwSavedStates[i++]);
		Gos3D::SetTextureStageState( 0, D3DTSS_MIPFILTER,		m_dwSavedStates[i++]);
		Gos3D::SetTextureStageState( 0, D3DTSS_TEXCOORDINDEX,	m_dwSavedStates[i++]);
		Gos3D::SetTextureStageState( 0, D3DTSS_TEXTURETRANSFORMFLAGS, m_dwSavedStates[i++]);
		Gos3D::SetTextureStageState( 1, D3DTSS_COLOROP,			m_dwSavedStates[i++]);
		Gos3D::SetTextureStageState( 1, D3DTSS_ALPHAOP,			m_dwSavedStates[i++]);
	}
	if(m_pSavedVertexShader)
		Gos3D::SetVertexShader(m_pSavedVertexShader);
	if(m_pSavedVertexBuffer)
		Gos3D::SetStreamSource(0, m_pSavedVertexBuffer, m_uiSavedStride);
}


void Text::RenderCachedText(CacheEntry *pText)
{
	float				xPos;
	float				yPos;
	float				scale;
	int					triangleCount;
	IDirect3DTexture8 *	pCurrentTexture;
	Font2DVertex*		pVertices;
	HRESULT				rc;

	SetTextRenderstates();
	triangleCount = 0;
	pCurrentTexture = NULL;

	scale = pText->pFont->Scale();

	pVertices = (Font2DVertex*)m_pGOSVertextBuffer->Lock(D3DLOCK_DISCARD);

	if(pVertices)
	{
		for(int i = 0; i<pText->iLineCount; i++)
		{
			CachedLine *	pLine = &pText->pLines[i];
			yPos = pLine->fYPos;
			if( pLine->iLength == -1 )
			{
				continue;
			}

			xPos = pLine->fXPos;

			for(int j=0;pLine->pPolyNodes[j];j++)
			{
				SurfaceNode * pNode = pLine->pPolyNodes[j];
				SurfaceCache * pSurface = pNode->pSurface;
				
				if(!pCurrentTexture)
				{
					pCurrentTexture = pSurface->pD3DTexture;
					rc = Graphics::D3DDevice()->SetTexture(0, pCurrentTexture);
				}
				else if(pCurrentTexture!=pSurface->pD3DTexture)
				{
					// Swapping textures so flush vertex buffer if it's not empty
					if(triangleCount!=0)
					{
						m_pGOSVertextBuffer->Unlock();
						Gos3D::DrawPrimitive(D3DPT_TRIANGLELIST, 0, triangleCount);
						pVertices = (Font2DVertex*)m_pGOSVertextBuffer->Lock(D3DLOCK_DISCARD);
						triangleCount = 0;
					}
					pCurrentTexture = pSurface->pD3DTexture;
					rc = Graphics::D3DDevice()->SetTexture(0, pCurrentTexture);
				}

				if(rc == D3D_OK)
				{
					// Fragment could be completely left clipped or right clipped
					// (but can never be completely top or bottom clipped)
					if( (xPos+pNode->iWidth*scale) >= s_RegionLeft && xPos < s_RegionRight )
					{

						float	x0, y0;
						float	y1 = yPos+pNode->iHeight*scale;
						float	x1 = xPos+pNode->iWidth*scale;
						float	u0 = (0.5f+((float)pNode->iLeft))/(float)c_SurfaceWidth;
						float	v0 = (0.5f+((float)pNode->iTop))/(float)c_SurfaceHeight;
						float	u1 = ((float)pNode->iLeft+pNode->iWidth)/(float)c_SurfaceWidth;
						float	v1 = ((float)pNode->iTop+pNode->iHeight)/(float)c_SurfaceHeight;
						float	ooScale = 1.f/scale;

						// clip left
						if(xPos<(float)s_RegionLeft)
						{
							u0 += ooScale * (((float)s_RegionLeft) - xPos) / (float)c_SurfaceWidth;
							x0 = (float)s_RegionLeft;
						}
						else
							x0 = xPos;
						
						// clip right
						if(x1>=(float)s_RegionRight)
						{
							u1 -= ooScale * ( x1 - ((float)s_RegionRight+1.f)) / (float)c_SurfaceWidth;
							x1 = (float)s_RegionRight-1;
						}
						
						// clip top
						if(yPos<(float)s_RegionTop)
						{
							v0 += ooScale * (((float)s_RegionTop) - yPos) / (float)c_SurfaceHeight;
							y0 = (float)s_RegionTop;
						}
						else
							y0 = yPos;
						
						// clip bottom
						if(y1>=(float)s_RegionBottom)
						{
							v1 -= ooScale * ( y1 - ((float)s_RegionBottom+1.f)) / (float)c_SurfaceHeight;
							y1 = (float)s_RegionBottom-1;
						}

						*pVertices++ = Init2DFontVertex( x0,	y1,	0.9f, 1.0f, s_ForegroundColor, u0,v1);
						*pVertices++ = Init2DFontVertex( x0,	y0,	0.9f, 1.0f, s_ForegroundColor, u0,v0);
						*pVertices++ = Init2DFontVertex( x1,	y0,	0.9f, 1.0f, s_ForegroundColor, u1,v0);
						*pVertices++ = Init2DFontVertex( x1,	y0,	0.9f, 1.0f, s_ForegroundColor, u1,v0);
						*pVertices++ = Init2DFontVertex( x1,	y1,	0.9f, 1.0f, s_ForegroundColor, u1,v1);
						*pVertices++ = Init2DFontVertex( x0,	y1,	0.9f, 1.0f, s_ForegroundColor, u0,v1);
						triangleCount+=2;
					}

					// Flush vertex buffer if it's about to overflow
					if(triangleCount*3 > (c_VertexBufferSize-6))
					{
						// Unlock and render the vertex buffer
						m_pGOSVertextBuffer->Unlock();
						Gos3D::DrawPrimitive( D3DPT_TRIANGLELIST, 0, triangleCount);
						triangleCount = 0;
						pVertices = (Font2DVertex*)m_pGOSVertextBuffer->Lock(D3DLOCK_DISCARD);
					}
					
					xPos+=pNode->iWidth*scale;
				}
			}
		}

		// Flush any remaining triangles
		m_pGOSVertextBuffer->Unlock();
		if(triangleCount!=0)
			Gos3D::DrawPrimitive(D3DPT_TRIANGLELIST, 0, triangleCount);
	}

	// Update MRU/LRU lists
	if(!m_pLRU&&!m_pMRU)
	{
		m_pLRU=m_pMRU=pText;
		pText->pLRU=pText->pMRU=NULL;
	}
	else if(pText!=m_pMRU)
	{
		// Remove from MRU/LRU lists
		if(pText->pLRU)
			pText->pLRU->pMRU = pText->pMRU;
		if(pText->pMRU)
			pText->pMRU->pLRU = pText->pLRU;
	
		if(pText==m_pLRU)
		{
			m_pLRU = pText->pMRU;
			m_pLRU->pLRU = NULL;
		}
		
		// Put at head of MRU list
		m_pMRU->pMRU = pText;
		pText->pLRU = m_pMRU;
		m_pMRU = pText;
		pText->pMRU = NULL;
	}

	pText->iFrameUsed = App::s_frameNo;


	ResetTextRenderstates();

}


void Text::RenderLineFromTexture( RECT *pBounds, float XPosition, float YPosition, float Scale)
{
	float height = (float)pBounds->bottom;
	float width = (float)pBounds->right;

	// Fragment could be completely left clipped or right clipped
	// (but can never be completely top or bottom clipped)
	if( (XPosition+width*Scale) >= (float)s_RegionLeft && XPosition < (float)s_RegionRight )
	{
		float	x0, y0;
		float	y1 = YPosition+height*Scale;
		float	x1 = XPosition+width*Scale;
		float	u0 = 0.5f/(float)c_SurfaceWidth;
		float	v0 = 0.5f/(float)c_SurfaceHeight;
		float	u1 = ((float)width)/(float)c_SurfaceWidth;
		float	v1 = ((float)height)/(float)c_SurfaceHeight;
		float	ooScale = 1.f/Scale;

		// clip left
		if(XPosition<(float)s_RegionLeft)
		{
			u0 += ooScale * (((float)s_RegionLeft) - XPosition) / (float)c_SurfaceWidth;
			x0 = (float)s_RegionLeft;
		}
		else
			x0 = XPosition;
						
		// clip right
		if(x1>=(float)s_RegionRight)
		{
			u1 -= ooScale * ( x1 - ((float)s_RegionRight+1.f)) / (float)c_SurfaceWidth;
			x1 = (float)s_RegionRight-1;
		}
						
		// clip top
		if(YPosition<(float)s_RegionTop)
		{
			v0 += ooScale * (((float)s_RegionTop) - YPosition) / (float)c_SurfaceHeight;
			y0 = (float)s_RegionTop;
		}
		else
			y0 = YPosition;
		
		// clip bottom
		if(y1>=(float)s_RegionBottom)
		{
			v1 -= ooScale * ( y1 - ((float)s_RegionBottom+1.f)) / (float)c_SurfaceHeight;
			y1 = (float)s_RegionBottom-1;
		}
	
	
	
		// Fill vertex buffer
		Font2DVertex* pVertices = (Font2DVertex*)m_pGOSVertextBuffer->Lock();

		*pVertices++ = Init2DFontVertex( x0,	y1,	0.9f, 1.0f, s_ForegroundColor, u0,v1);
		*pVertices++ = Init2DFontVertex( x0,	y0,	0.9f, 1.0f, s_ForegroundColor, u0,v0);
		*pVertices++ = Init2DFontVertex( x1,	y0,	0.9f, 1.0f, s_ForegroundColor, u1,v0);
		*pVertices++ = Init2DFontVertex( x1,	y1,	0.9f, 1.0f, s_ForegroundColor, u1,v1);

		// Unlock and render the vertex buffer
		m_pGOSVertextBuffer->Unlock();
		Gos3D::DrawPrimitive( D3DPT_TRIANGLEFAN, 0, 2 );
	}
}


void Text::DrawBackground( int Left, int Top, int Right, int Bottom, DWORD Color )
{
	GOS_ASSERT( Left<Right&&Top<Bottom)

	// All coordinates are relative, so add region bounds
	// to get relative values
	Left	+= s_RegionLeft;
	Right	+= s_RegionLeft;
	Top		+= s_RegionTop;
	Bottom	+= s_RegionTop;

	if( Left>s_RegionRight||Top>s_RegionBottom )
		return;

	Left	= Left	> s_RegionLeft	? Left	: s_RegionLeft;
	Right	= Right	< s_RegionRight	? Right	: s_RegionRight;
	Top		= Top	> s_RegionTop	? Top	: s_RegionTop;
	Bottom	= Bottom< s_RegionBottom? Bottom: s_RegionBottom;


	s_pText->SetTextRenderstates();

	Graphics::D3DDevice()->SetTexture(0, NULL);
		
	// Fill vertex buffer
	Font2DVertex* pVertices = (Font2DVertex*)Text::GetTextObj()->m_pGOSVertextBuffer->Lock();

	*pVertices++ = Text::GetTextObj()->Init2DFontVertex( (float)Left,	(float)Bottom,	0.99f, 1.0f, Color, 0.f,0.f);
	*pVertices++ = Text::GetTextObj()->Init2DFontVertex( (float)Left,	(float)Top,		0.99f, 1.0f, Color, 0.f,0.f);
	*pVertices++ = Text::GetTextObj()->Init2DFontVertex( (float)Right,	(float)Top,		0.99f, 1.0f, Color, 0.f,0.f);
	*pVertices++ = Text::GetTextObj()->Init2DFontVertex( (float)Right,	(float)Bottom,	0.99f, 1.0f, Color, 0.f,0.f);

	// Unlock and render the vertex buffer
	Text::GetTextObj()->m_pGOSVertextBuffer->Unlock();
	Gos3D::DrawPrimitive( D3DPT_TRIANGLEFAN, 0, 2 );

	s_pText->ResetTextRenderstates();
}


void Text::StringBounds( DWORD* pWidth, DWORD* pHeight, const _TCHAR *Message, ... )
{
    va_list		args;
	
	if(!s_pFont)
	{
		STOP(_T( "No font selected" ));
		return;
	}

	if( !Message || *Message == 0 )
	{
		*pWidth = 0;
		*pHeight = ((Font3D*)s_pFont)->Height();
		return;
	}

    va_start( args, Message );	
	s_pText->m_iStrln = _vsntprintf(s_pText->m_szTextBuffer, sizeof(s_pText->m_szTextBuffer), Message, args);
    va_end( args );

	if(s_pText->m_iStrln<0)
	{
		STOP(_T("String too long"));
		return;
	}
	
	((Font3D*)s_pFont)->StringBounds(s_pText,pWidth, pHeight);

}


bool Font3D::Initialize()
{
	bool isOK = false;

	TTFont *pFont = new TTFont();

	if(pFont)
	{
		pFont->SetDC(CreateCompatibleDC(NULL));
		
		if(pFont->GetDC())
		{
			// Prepare to create a bitmap (TEMPORARY until we get GDI stuff from XBox team)
			BITMAPINFO bmi;
			VOID *pBits;
			ZeroMemory( &bmi.bmiHeader,  sizeof(BITMAPINFOHEADER) );
			bmi.bmiHeader.biSize        = sizeof(BITMAPINFOHEADER)+sizeof(DWORD);
			bmi.bmiHeader.biWidth       =  (int)c_SurfaceWidth;
			bmi.bmiHeader.biHeight      = -(int)c_SurfaceHeight;
			bmi.bmiHeader.biPlanes      = 1;
			bmi.bmiHeader.biCompression = BI_RGB ;
			bmi.bmiHeader.biBitCount    = 32;

			// Create a DC and a bitmap for the font
			pFont->SetBMP(CreateDIBSection( pFont->GetDC(), &bmi, DIB_RGB_COLORS,(VOID**)&pBits, NULL, 0 ));

			if(pFont->GetBMP()&&pBits)
			{
				pFont->SetBMPBits((DWORD*)pBits);
				SelectObject( pFont->GetDC(), pFont->GetBMP() );
				SetTextColor( pFont->GetDC(), RGB(255,255,255) );
				SetBkColor(   pFont->GetDC(), 0x00000000 );
				SetTextAlign( pFont->GetDC(), TA_TOP );

				s_pFont = pFont;
				isOK = true;

			}
		}
		
		s_pGlyphCache= new SurfaceCache;

		if(s_pGlyphCache )
		{
			HRESULT rc;

			memset(s_pGlyphCache,0,sizeof(SurfaceCache));
			rc =  Graphics::D3DDevice()->CreateTexture(256, 256, 1, 0, D3DFMT_A4R4G4B4, D3DPOOL_MANAGED, &s_pGlyphCache->pD3DTexture );
			if(FAILED(rc))
			{
				isOK = false;
			}
			
			s_pGlyphCache->pTree = (Text::GetTextObj())->GetFreeNode();
			s_pGlyphCache->pTree->iWidth = 256;
			s_pGlyphCache->pTree->iHeight = 256;
		}

		s_iGlyphSpace = 256-32;
		s_ppGlyphNodes = (SurfaceNode**)Text::GetTextHeap()->Malloc(s_iGlyphSpace*sizeof(SurfaceNode**));
		memset(s_ppGlyphNodes,0,s_iGlyphSpace*sizeof(SurfaceNode**));

		s_PixelLookup[0] = 0;
		
		for( WORD i = 1; i < 16; i++ )
			s_PixelLookup[i] = (i<<12) | 0x0fff;
	}

	return isOK;
}



void Font3D::Shutdown()
{
	if(s_pFont)
	{
		if(s_hBMP)
			DeleteObject(s_hBMP);
		if(s_hDC)
			DeleteObject(s_hDC);

		if(s_pGlyphCache)
		{
			if(s_pGlyphCache->pD3DTexture)
				s_pGlyphCache->pD3DTexture->Release();

			delete	s_pGlyphCache;
		}

		if(s_ppGlyphNodes)
			Text::GetTextHeap()->Free(s_ppGlyphNodes);

		delete s_pFont;
		s_pFont = (Font3D*)NULL;
	}

}


Font3D::Font3D()
{
	memset(m_szFontFile, 0, sizeof(m_szFontFile));
	m_iHeight = 0;
	m_iPointSize = 12;
	m_fScale = 1.f;
	m_bBold = false;
	m_bItalic = false;

}


Font3D::~Font3D()
{
}


GosFont3D *Font3D::CreateFromFile( TCHAR *pFileName, DWORD StartLine, int CharCount )
{	
	Font3D *pFont = NULL;
	
	//
	// Support for TrueType Fonts
	//
	if( !_tcsnicmp(_T(".TTF"), &pFileName[_tcslen(pFileName)-4],4) || !_tcsnicmp(_T(".TTC"), &pFileName[_tcslen(pFileName)-4],4))
	{
		pFont = new TTFont;

		if(pFont)
		{
			_tcscpy(pFont->m_szFontFile, pFileName);
		}
		else
			STOP(_T("Could not create GosFont3D object"));

	}
	//
	// Support for .d3f font files
	//
	else if(!_tcsnicmp(_T(".D3F"), &pFileName[_tcslen(pFileName)-4],4))
	{
		pFont = new D3FFont;

		if(pFont)
		{
			_tcscpy(pFont->m_szFontFile, pFileName);
			pFont->m_iCharCount = CharCount;
			pFont->CreateFont();
		}
		else
			STOP(_T("Could not create GosFont3D object"));


	}
	
	return pFont;
}


GosFont3D *Font3D::CreateFromTexture( GosTexture *pTexture, DWORD StartLine, int CharCount )
{
	_asm int 3;
	return (GosFont3D *)NULL;
}


void Font3D::FlushNode(SurfaceNode *pNode)
{
	if( pNode)
	{
		FlushNode(pNode->pChildX);
		FlushNode(pNode->pChildY);

		Text::GetTextObj()->FreeNode(pNode);
	}
}


void Font3D::FlushGlyphCache()
{
	FlushNode(s_pGlyphCache->pTree->pChildX);
	FlushNode(s_pGlyphCache->pTree->pChildY);

	s_pGlyphCache->pTree->pChildX=NULL;
	s_pGlyphCache->pTree->pChildY=NULL;
}


void Font3D::FillBlock( D3DLOCKED_RECT *pDst, RECT *pBounds, WORD Color)
{
	WORD* pDst16 = (WORD*)(((int)pDst->pBits)+pBounds->top*pDst->Pitch+(pBounds->left<<1));

	for( int y=0; y < pBounds->bottom-pBounds->top; y++ )
	{
		WORD* pD = pDst16;
		for( int x=0; x < pBounds->right-pBounds->left; x++ )
			*pD++= Color;

		pDst16 += pDst->Pitch >> 1;
	}
}


TTFont::TTFont()
{
	m_hFont = NULL;
}


TTFont::~TTFont()
{
	if(m_hFont)
		DeleteObject(m_hFont);
}


void TTFont::CreateFont()
{

	LOGFONT			lf;
	TEXTMETRIC		tm;
	HFONT			oldFont;
	DWORD			height;
	_TCHAR			space[2];


	if (m_hFont)
		DeleteObject(m_hFont);
	
	memset(&lf, 0, sizeof(lf));

	lf.lfHeight		= MulDiv ((int)(m_iPointSize*m_fScale), GetDeviceCaps (s_hDC, LOGPIXELSY), 72);
	lf.lfWeight		= m_bBold ? FW_BOLD : FW_NORMAL;
	lf.lfItalic		= m_bItalic;
	lf.lfQuality	= ANTIALIASED_QUALITY ;
	lf.lfCharSet	= DEFAULT_CHARSET;

	_tcsncpy(lf.lfFaceName, m_szFontFile, _tcslen(m_szFontFile)-4);

	m_hFont = CreateFontIndirect(&lf);

	GOS_ASSERT(m_hFont)

	oldFont = (HFONT)SelectObject( s_hDC, m_hFont);
	GetTextMetrics(s_hDC, &tm);
	m_iHeight = tm.tmHeight;

	GOS_ASSERT(m_iHeight<c_SurfaceHeight);

	m_iWidth = tm.tmAveCharWidth;
	SelectObject( s_hDC, oldFont);

	space[0] = ' ';
	space[1] = 0;
	LineBounds( &m_dwSpaceWidth, &height, space);

}


int TTFont::StringBounds( Text *pText, DWORD* pWidth, DWORD* pHeight)
{
	DWORD		lineWidth;
	DWORD		lineHeight;
	_TCHAR		*pString = pText->Buffer();
	int			lineCount = 0;
	DWORD		maxWidth = 0;
	DWORD		totalHeight = 0;
	int			count;

	UpdateFont();

	if(m_hFont)
	{
		while(*pString)
		{
			if( *pString=='\n' || *pString == 0x0a || *pString == 0x0d)
			{
				totalHeight+=m_iHeight;
				lineCount++;
				pString++;
				continue;
			}

			count=LineBounds(&lineWidth,&lineHeight,pString);
			
			if(pString[count]=='\n'||pString[count]==0x0a||pString[count]==0x0d)
				count+=1;

			pString+=count;
			lineCount++;
			totalHeight+=m_iHeight;
			if(maxWidth<lineWidth)
				maxWidth=lineWidth;
		}
	}
	*pWidth = (DWORD)(ceil(maxWidth*m_fScale));
	*pHeight = (DWORD)(ceil(totalHeight*m_fScale));

	return lineCount;
}


int TTFont::LineBounds( DWORD* pWidth, DWORD* pHeight, _TCHAR* pString)
{
	int			count = 0;

	if(m_hFont)
	{
		_TCHAR *	pTmp = pString;
		
		while(*pTmp && *pTmp!='\n' && *pTmp!=0x0a && *pTmp!=0x0d)
		{
			count++;
			pTmp++;
		}
	}
		
	if(count)
	{	
		SIZE	ssize;
		HFONT	oldFont = (HFONT)SelectObject( s_hDC, m_hFont);
			
		GetTextExtentPoint32(s_hDC, pString, count, &ssize);
		*pWidth = ssize.cx;
		*pHeight = ssize.cy;
			
		SelectObject( s_hDC, oldFont);
	}
	else
	{
		*pWidth = 0;
		*pHeight = m_iHeight;
	}

	return count;
}


int TTFont::WordWidth(DWORD* pWidth, _TCHAR *pString)
{
	int			count = 0;

	if(m_hFont)
	{
		_TCHAR *	pTmp = pString;
				
		while(*pTmp && *pTmp!=' ' && *pTmp!='\n' && *pTmp!=0x0a && *pTmp!=0x0d)
		{
			count++;
			pTmp++;
		}
	}

	if(count)
	{	
		SIZE		ssize;
		HFONT		oldFont = (HFONT)SelectObject( s_hDC, m_hFont);
		
		GetTextExtentPoint32(s_hDC, pString, count, &ssize);
		*pWidth = ssize.cx;
		
		SelectObject( s_hDC, oldFont);
	}
	else
		*pWidth = 0;

	return count;
}


void TTFont::UpdateFont()
{
	bool		bold;
	bool		italic;

	Text::Get(GosText::Bold,&bold);
	Text::Get(GosText::Italic,&italic);

	if(!m_hFont||m_iPointSize!=(int)(Text::GetPointSize()*Text::GetScale())||m_bBold!=bold||m_bItalic!=italic)
	{
		m_iPointSize = (int)(Text::GetPointSize()*Text::GetScale());
		m_bBold = bold;
		m_bItalic = italic;
		CreateFont();
	}
}


void TTFont::DrawStringToTexture(Text *pText, CacheEntry *pEntry)
{
	if(m_hFont)
	{
		int				width;
		int				lineIndex;
		int				polyIndex;
		int				xOffset;
		D3DLOCKED_RECT	d3dlr;
		RECT			rect;
		HRESULT			rc;
		HFONT			oldFont;
		CachedLine *	pCurrentLine;
		LineOfText *	pLineOfText;
			
		oldFont = (HFONT)SelectObject( s_hDC, m_hFont);

		lineIndex = 0;
		pLineOfText = pText->GetLine(lineIndex);

		while(pLineOfText->pStart)
		{
			pCurrentLine = &pEntry->pLines[lineIndex];
			pCurrentLine->pString=pLineOfText->pStart;

			if(pLineOfText->iCount == 0)
			{
				pCurrentLine->iLength=-1;
				lineIndex++;
				continue;
			}

			width=pCurrentLine->iWidth=pLineOfText->iWidth;
			pCurrentLine->iHeight=pLineOfText->iHeight;
			pCurrentLine->fYPos = pLineOfText->fYPos;
			pCurrentLine->fXPos = pLineOfText->fXPos;

			polyIndex=0;
			xOffset=0;
			while(((int)width)>0)
			{
				SurfaceNode *	pCurrentNode;

				pCurrentNode = pText->FindFreeNode(pEntry,width,pCurrentLine->iHeight);
				width-=pCurrentNode->iWidth;
				pCurrentLine->pPolyNodes[polyIndex] = pCurrentNode;
				polyIndex++;

				rect.top = pCurrentNode->iTop;
				rect.left = pCurrentNode->iLeft;
				rect.right = rect.left+pCurrentNode->iWidth;
				rect.bottom = rect.top+pCurrentNode->iHeight;

				ExtTextOut( s_hDC, xOffset, 0, ETO_OPAQUE, &rect, pCurrentLine->pString, pLineOfText->iCount, NULL );
				xOffset-=pCurrentNode->iWidth;

				
				rc = pCurrentNode->pSurface->pD3DTexture->LockRect(0,&d3dlr,0,0);
								
				DrawBitsToTexture(&d3dlr, s_pBMPBits, &rect);

				rc =pCurrentNode->pSurface->pD3DTexture->UnlockRect(0);
				if(FAILED(rc)) STOP((_T("Unable to unlock texture")));
			}
	
			pCurrentLine->iLength=pLineOfText->iCount;
			pCurrentLine->pPolyNodes[polyIndex] = NULL;
			lineIndex++;
			pLineOfText = pText->GetLine(lineIndex);
		}
		SelectObject( s_hDC, oldFont);
	}
}


void TTFont::DrawBitsToTexture(D3DLOCKED_RECT *pD3Dlr, DWORD *pSrcBits, RECT *pBounds)
{
	int		height = pBounds->bottom-pBounds->top;
	int		width = pBounds->right-pBounds->left;
	WORD*	pDst16 = (WORD*)(((int)pD3Dlr->pBits)+pBounds->top*pD3Dlr->Pitch+(pBounds->left<<1));


	for( int y=0; y < height; y++ )
	{
		WORD* pDst = pDst16;

		for( int x=0; x < width; x++ )
			*pDst++ = s_PixelLookup[(pSrcBits[c_SurfaceWidth*y + x] & 0xf0)>>4];

		pDst16 += pD3Dlr->Pitch >> 1;
	}	
}


void TTFont::DrawUncachedText(Text *pText)
{
	if(m_hFont)
	{
		int				width;
		int				lineIndex;
		int				height;
		float			xPosition;
		float			yPosition;
		int				texXOffset;
		int				texWidth;
		RECT			rect;
		HFONT			oldFont;
		LineOfText *	pLineOfText;

		oldFont = (HFONT)SelectObject( s_hDC, m_hFont);
		
		pText->SetTextRenderstates();
		pText->SetDefaultTexture();

		lineIndex = 0;
		pLineOfText = pText->GetLine(lineIndex);

		while(pLineOfText->pStart)
		{
			if(pLineOfText->iCount == 0)
			{
				lineIndex++;
				continue;
			}

			width = pLineOfText->iWidth;
			xPosition = pLineOfText->fXPos;
			yPosition = pLineOfText->fYPos;
			
			if( pLineOfText->iHeight > pText->GetScratchHeight() )
				height = pText->GetScratchHeight();
			else
				height = pLineOfText->iHeight;

			texXOffset = 0;
			
			while(((int)width)>0)
			{

				D3DLOCKED_RECT	d3dlr;
				SurfaceCache *	pSC;

				texWidth = width>c_SurfaceWidth?c_SurfaceWidth:width;
				rect.top = 0;
				rect.left = 0;
				rect.bottom = height;
				rect.right = texWidth;

				ExtTextOut( s_hDC, texXOffset, 0, ETO_OPAQUE|ETO_CLIPPED, &rect, pLineOfText->pStart, pLineOfText->iCount, NULL );

				pSC = pText->LockSurface(0,&d3dlr);
				DrawBitsToTexture(&d3dlr,(DWORD*)s_pBMPBits, &rect);
				pText->UnlockSurface(pSC);

				pText->RenderLineFromTexture( &rect, xPosition-texXOffset, yPosition, m_fScale);

				texXOffset-=texWidth;
				width-=texWidth;
			}

			lineIndex++;
			pLineOfText = pText->GetLine(lineIndex);
		}

		pText->ResetTextRenderstates();
		SelectObject( s_hDC, oldFont);
	}
}


D3FFont::D3FFont()
{
	m_pFontData = NULL;
	m_ppChrBits = NULL;
}


D3FFont::~D3FFont()
{	
	if(m_pFontData)
		Text::GetTextHeap()->Free(m_pFontData);
	if(m_ppChrBits)
		Text::GetTextHeap()->Free(m_ppChrBits);
		
}


void D3FFont::UpdateFont()
{
	int height;

	Text::Get(GosText::Bold,&m_bBold);
	Text::Get(GosText::Italic,&m_bItalic);
	Text::Get(GosText::Proportional,&m_bProportional);

	m_iPointSize = Text::GetPointSize();
	m_fScale = Text::GetScale();
	

	GetCharSize((int*)&m_dwSpaceWidth, &height, 32 );
}


void D3FFont::CreateFont()
{
	GosFile * pFile = GosFile::Read(m_szFontFile);

	if(pFile)
	{
		D3DFontData * pFontData = (D3DFontData*)(pFile->Data());
		GOS_ASSERT(pFontData);
		GOS_ASSERT(pFontData->dwSig=='4F3D');

		int cCount= m_iCharCount-32;

		m_pFontData		= (D3DFontTexture*)Text::GetTextHeap()->Malloc(pFile->Size()-sizeof(D3DFontData));
		m_ppChrBits		= (BYTE**)Text::GetTextHeap()->Malloc(cCount*sizeof(BYTE*)+3*cCount*sizeof(BYTE)+cCount*sizeof(WORD));	
		m_pCharWidth	= (BYTE*)((int)m_ppChrBits)+cCount*sizeof(BYTE*);
		m_pCharHeight	= m_pCharWidth+cCount*sizeof(BYTE);	
		m_pTopOffset	= m_pCharHeight+cCount*sizeof(BYTE);
		m_pCharStride	= (WORD*)(m_pTopOffset+cCount*sizeof(BYTE));


		memcpy(m_pFontData, pFontData+1, pFile->Size()-sizeof(D3DFontData));

		m_iHeight = pFontData->dwFontHeight-2;
		m_iWidth = pFontData->bW[0];
		m_iMaxWidth = 0;

		BYTE lastID = 255;

		for( int chr=33; chr<m_iCharCount; chr++ )
		{
			int ichr = chr-32;
			
			// Find the start of the data containing this chr
			D3DFontTexture * pTexture;		
			DWORD dwTextureSize;
			BYTE texID = pFontData->bTexture[chr];
			
			if( lastID!=texID )
			{
				lastID = texID;
				pTexture = m_pFontData;
				dwTextureSize = pTexture->dwSize;

				while(texID--)
				{
					pTexture = (D3DFontTexture*)(((int)pTexture)+sizeof(DWORD)+dwTextureSize*dwTextureSize);
					dwTextureSize = pTexture->dwSize;
				}
			}

			m_pCharStride[ichr] = (WORD)dwTextureSize;
			
			// Find offset from top and height of character
			BYTE *pBits = m_ppChrBits[ichr] = pTexture->bPixels+pFontData->bX[chr]+(pFontData->bY[chr]+2)*pTexture->dwSize;
			m_pTopOffset[ichr] = 0;
			m_pCharHeight[ichr] = (BYTE)m_iHeight;
			m_pCharWidth[ichr] = (BYTE)(pFontData->bW[chr]-1);
			m_iMaxWidth = m_iMaxWidth>m_pCharWidth[ichr]?m_iMaxWidth:m_pCharWidth[ichr];

			bool found = false;
			
			do
			{
				for( int i=0; i< m_pCharWidth[ichr]; i++ )
				{
					if(pBits[i])
					{
						found = true;
						break;
					}
				}											// Find blank lines ontop of character
				if( !found )
				{
					pBits+=pTexture->dwSize;
					m_pTopOffset[ichr]++;
					m_pCharHeight[ichr]--;
				}
			} while( !found && m_pCharHeight[ichr] );

			pBits += m_pCharHeight[ichr]*pTexture->dwSize;
			found = false;

			if(m_pCharHeight[ichr])
			{
				do
				{
					pBits -= pTexture->dwSize;
					for( int i=0; i<m_pCharWidth[ichr]; i++ )
					{
						if(pBits[i])
						{
							found = true;
							break;								// Find lines character takes up
						}
					}
					if( !found )
						m_pCharHeight[ichr]--;
				} while( !found && m_pCharHeight[ichr] );
			}

		}

		delete pFile;
		m_iMaxWidth++;
	}
}


int D3FFont::StringBounds( Text *pText, DWORD* pWidth, DWORD* pHeight)
{
	DWORD		lineWidth;
	DWORD		lineHeight;
	_TCHAR		*pString = pText->Buffer();
	int			lineCount = 0;
	DWORD		maxWidth = 0;
	DWORD		totalHeight = 0;
	int			count;

	UpdateFont();
			
	while(*pString)
	{
		if( *pString=='\n' || *pString == 0x0a || *pString == 0x0d)
		{
			totalHeight+=m_iHeight;
			lineCount++;
			pString++;
			continue;
		}

		count=LineBounds(&lineWidth,&lineHeight,pString);
			
		if(pString[count]=='\n'||pString[count]==0x0a||pString[count]==0x0d)
			count+=1;

		pString+=count;
		lineCount++;
		totalHeight+=m_iHeight;
		if(maxWidth<lineWidth)
			maxWidth=lineWidth;
	}

	*pWidth = (DWORD)(ceil(maxWidth*m_fScale));
	*pHeight = (DWORD)(ceil(totalHeight*m_fScale));

	return lineCount;
}


int D3FFont::LineBounds( DWORD* pWidth, DWORD* pHeight, _TCHAR* pString)
{

	int			charwidth;
	int			charheight;
	int			linewidth = 0;
	int			maxheight = 0;
	int			count = 0;

	while(*pString && *pString!='\n' && *pString!=0x0a && *pString!=0x0d)
	{
		count++;
		GetCharSize(&charwidth,&charheight,*pString++);
		linewidth+=charwidth;
		maxheight=charheight>maxheight?charheight:maxheight;
	}
		
	if(m_bItalic && m_bProportional&&count>0)
		linewidth+=(maxheight>>2);
	*pWidth = linewidth;
	if(count)
		*pHeight = maxheight;
	else
		*pHeight = m_iHeight;

	return count;
}

int D3FFont::WordWidth(DWORD* pWidth, _TCHAR *pString)
{
	int		count = 0;
	int		linewidth = 0;
	int		charwidth;
	int		charheight;
				
	while(*pString && *pString!=' ' && *pString!='\n' && *pString!=0x0a && *pString!=0x0d)
	{
		GetCharSize(&charwidth,&charheight,*pString++);
		linewidth+=charwidth;
		count++;
	}

	*pWidth = linewidth;

	return count;
}



void D3FFont::GetCharSize( int *pWidth, int *pHeight, _TCHAR Chr )
{
	int width;
	
	if (Chr < 32 || Chr >=m_iCharCount)
		Chr = '@';

	*pHeight=m_iHeight;

	if( Chr==32 )
	{
		if( m_bProportional )
			width=(m_iWidth>>1)+(m_iWidth>>2);
		else
			width=m_iMaxWidth;
	}
	else
	{
		if( m_bProportional )
			width=m_pCharWidth[Chr-32]+1;
		else
		{
			width=m_iMaxWidth;
			if(m_bItalic)
				width+=(m_iHeight>>2);
		}
		if( m_bBold )
			width+=1;
	}
	*pWidth = width;
	return;
}


int D3FFont::MaxWidth()
{
	if(!m_bItalic)
		return m_iMaxWidth+(m_bBold?1:0);
	return (m_iHeight>>2)+m_iMaxWidth+(m_bBold?1:0);
	
}



int D3FFont::BltChar( SurfaceNode *pNode, D3DLOCKED_RECT *pSrc, D3DLOCKED_RECT *pDst, int XOffset, RECT *pClipRect, _TCHAR Chr )
{
	int top, left, right, bottom, height;
	int	srcWidth,dstWidth;
	int clip = 0;

	Chr-=32;

	left = pNode->iLeft;;
	top = pNode->iTop+m_pTopOffset[Chr];
	bottom = top+m_pCharHeight[Chr];
	right = left+m_pCharWidth[Chr];
	height = bottom-top;

	if(m_bBold)
		right+=1;

	if(m_bProportional)
		dstWidth = 1 + right - left;
	else 
		dstWidth = MaxWidth();

	srcWidth = right-left;

	if(XOffset<0)
		clip|=1;
	if((XOffset+pClipRect->left+srcWidth)>=pClipRect->right)
		clip|=2;


	switch(clip)
	{
	case 1:			// left clipped (XOffset is -ve)
		left -= XOffset;
		break;
	case 2:			// right clipped
		right = left+(pClipRect->right-(pClipRect->left+XOffset));
		break;
	case 3:			// left and right clipped
		right = left+(pClipRect->right-(pClipRect->left+XOffset));
		left -= XOffset;
		break;
	}

	WORD* pDst16 = (WORD*)(((int)pDst->pBits)+(pClipRect->top+m_pTopOffset[Chr]+height-1)*pDst->Pitch+((pClipRect->left+(XOffset>0?XOffset:0))<<1));
	WORD* pSrc16 = (WORD*)(((int)pSrc->pBits)+(top+height-1)*pSrc->Pitch+(left<<1));

	for( int y=0; y < height; y++ )
	{
		WORD* pD = pDst16;
		WORD* pS = pSrc16;
		for( int x=0; x < right-left; x++ )
			pD[x]=pS[x];

		pDst16 -= pDst->Pitch >> 1;
		pSrc16 -= pSrc->Pitch >> 1;
	}
	return dstWidth;

}


int D3FFont::BltItalicChar( SurfaceNode *pNode, D3DLOCKED_RECT *pSrc, D3DLOCKED_RECT *pDst, int XOffset, RECT *pClipRect, _TCHAR Chr )
{
	int top, bottom, height;
	int botLeft, botRight, topLeft, topRight;
	int dstWidth, srcWidth;
	int clipLeft, clipRight;
	int clip = 0;

	Chr-=32;

	clipLeft = botLeft = pNode->iLeft;
	botRight = botLeft+m_pCharWidth[Chr];
	topLeft = botLeft+(m_pCharHeight[Chr]>>2);
	clipRight = topRight = topLeft+m_pCharWidth[Chr];

	top = pNode->iTop+m_pTopOffset[Chr];
	bottom = top+m_pCharHeight[Chr];

	if(m_bBold)
	{
		botRight+=1;
		topRight+=1;
		clipRight+=1;
	}

	if(!m_bProportional)
	{
		dstWidth = MaxWidth();
	}
	else 
	{
		dstWidth = 1 + botRight - botLeft;
	}


	srcWidth =  topRight-botLeft;
	height = bottom-top;

	if(XOffset<0)
		clip|=1;
	if((XOffset+pClipRect->left+srcWidth)>=pClipRect->right)
		clip|=2;


	switch(clip)
	{
	case 1:			// left clipped (XOffset is -ve)
		clipLeft -= XOffset;
		break;
	case 2:			// right clipped
		clipRight = clipLeft+(pClipRect->right-(pClipRect->left+XOffset));
		break;
	case 3:			// left and right clipped
		clipRight = clipLeft+(pClipRect->right-(pClipRect->left+XOffset));
		clipLeft -= XOffset;
		break;
	}

	if(botRight>clipRight)
		botRight=clipRight;

	WORD* pDst16 = (WORD*)(((int)pDst->pBits)+(pClipRect->top+m_pTopOffset[Chr]+height-1)*pDst->Pitch+((pClipRect->left+(XOffset>0?XOffset:0))<<1));
	WORD* pSrc16 = (WORD*)(((int)pSrc->pBits)+(top+height-1)*pSrc->Pitch+(clipLeft<<1));

	for( int y=0; y < height; )
	{
		WORD*	pD = pDst16;
		WORD*	pS = pSrc16;

		for( int x=0; x < botRight-clipLeft; x++ )
			pD[x]=pS[x];

		pDst16 -= pDst->Pitch >> 1;
		pSrc16 -= pSrc->Pitch >> 1;

		if(!((++y)%4))
		{
			botLeft++;
			botRight++;

			if(botLeft>clipLeft)
			{
				clipLeft=botLeft;
				pDst16++;
				pSrc16++;
			}
			if(botRight>clipRight)
				botRight=clipRight;
		}
	}
	return dstWidth;
}


void D3FFont::DrawGlyph(SurfaceNode * pNode, D3DLOCKED_RECT *pDst, _TCHAR Chr)
{
	int		bott, left, height, width;
	WORD *	pDst16;
	BYTE*	pSrc;


	Chr-=32;

	height = m_pCharHeight[Chr];
	width = m_pCharWidth[Chr];
	left = pNode->iLeft;
	bott = pNode->iTop+m_pTopOffset[Chr]+height-1;

	pSrc = m_ppChrBits[Chr]+(m_pTopOffset[Chr]+height-1)*m_pCharStride[Chr];
	pDst16 = (WORD*)(((int)pDst->pBits)+bott*pDst->Pitch+(left<<1));

	if(!m_bItalic)
	{
		for( int y=0; y < height; y++ )
		{
			WORD* pD = pDst16;
			BYTE* pS = pSrc;
			for( int x=0; x < width; x++ )
				*pD++ = s_PixelLookup[(*pS++&0xf0)>>4];

			pDst16 -= pDst->Pitch >> 1;
			pSrc -= m_pCharStride[Chr];
		}

		if(m_bBold)
		{
			left+=1;

			pSrc = m_ppChrBits[Chr]+(m_pTopOffset[Chr]+height-1)*m_pCharStride[Chr];
			pDst16 = (WORD*)(((int)pDst->pBits)+bott*pDst->Pitch+(left<<1));

			for( int y=0; y < height; y++ )
			{
				WORD* pD = pDst16;
				BYTE* pS = pSrc;
				for( int x=0; x < width; x++ )
				{
					WORD src = s_PixelLookup[(*pS++&0xf0)>>4];
					*pD++ = *pD>src?*pD:src;
				}

				pDst16 -= pDst->Pitch >> 1;
				pSrc -= m_pCharStride[Chr];
			}
		}
	}
	else
	{
		for( int y=0; y < height; )
		{
			WORD* pD = pDst16;
			BYTE* pS = pSrc;
			for( int x=0; x < width; x++ )
				*pD++ = s_PixelLookup[(*pS++&0xf0)>>4];
			
			pDst16 -= pDst->Pitch >> 1;
			pSrc -= m_pCharStride[Chr];

			if(!((++y)%4))
				pDst16++;
		}

		if(m_bBold)
		{
			left+=1;

			pSrc = m_ppChrBits[Chr]+(m_pTopOffset[Chr]+height-1)*m_pCharStride[Chr];
			pDst16 = (WORD*)(((int)pDst->pBits)+bott*pDst->Pitch+(left<<1));

			for( int y=0; y < height; )
			{
				WORD* pD = pDst16;
				BYTE* pS = pSrc;
				for( int x=0; x < width; x++ )
				{
					WORD src = s_PixelLookup[(*pS++&0xf0)>>4];
					*pD++ = *pD>src?*pD:src;
				}

				pDst16 -= pDst->Pitch >> 1;
				pSrc -= m_pCharStride[Chr];

				if(!((++y)%4))
					pDst16++;
			}
		}
	}
}



SurfaceNode * D3FFont::IsCached(_TCHAR chr)
{
	if(s_ppGlyphNodes[chr-32])
		return s_ppGlyphNodes[chr-32];
	else return NULL;
}


void D3FFont::BltBlockToTexture(D3DLOCKED_RECT *pGlyph, D3DLOCKED_RECT *pDst, int XOffset, RECT *pClipRect, _TCHAR *pString)
{
	int				width;
	int				height;
	SurfaceNode *	pNode;
	_TCHAR			chr;

	if(XOffset<0)
	{
		while(XOffset<0)
		{
			GetCharSize(&width,&height,*pString++);
			XOffset+=width;
		}
		if( XOffset != 0)
		{
			XOffset-=width;
			pString--;
		}

		chr = pString[-1];

		// Italics are a pain..... This checks to see if the top-right of
		// the previous char has crossed into this region and draws it
		// if necessary....
		if(m_bItalic&&m_bProportional&&(-XOffset)<(m_iHeight>>2)&&chr!=32)
		{
			if(!(pNode = IsCached(chr)))
			{
				RECT bounds;

				if((pNode=Text::GetTextObj()->SearchTree(s_pGlyphCache->pTree, MaxWidth(), m_iHeight,true)))
				{
					s_ppGlyphNodes[chr-32]=pNode;
				}
				else
				{
					pNode=s_pGlyphCache->pTree->pChildX->pChildX;
				}
					bounds.top = pNode->iTop;
					bounds.left = pNode->iLeft;
					bounds.right = bounds.left+pNode->iWidth;
					bounds.bottom = bounds.top+pNode->iHeight;

					FillBlock(pGlyph, &bounds, 0x0000);
					DrawGlyph(pNode,pGlyph,chr);
			}

			GetCharSize(&width,&height,chr);			
			BltItalicChar(pNode, pGlyph, pDst, XOffset-width, pClipRect, chr);
		}
	}

	while(*pString&&pClipRect->left+XOffset<pClipRect->right)
	{
		chr = *pString++;
		
		// Italics REALLY are a pain.. The width returned by BltChar can't 
		// include the 25% extra (per line) that they require, so I need a
		// special check here to terminate italics
		if(m_bItalic&&(chr=='\n' || chr==0x0a || chr==0x0d))
			break;

		if( chr!=32)
		{
			if(!(pNode = IsCached(chr)))
			{
				if((pNode=Text::GetTextObj()->SearchTree(s_pGlyphCache->pTree, MaxWidth(), m_iHeight,true)))
				{
					s_ppGlyphNodes[chr-32]=pNode;
				}
				else
				{
					pNode=s_pGlyphCache->pTree->pChildX->pChildX;
				}

				RECT bounds;

				bounds.top = pNode->iTop;
				bounds.left = pNode->iLeft;
				bounds.right = bounds.left+pNode->iWidth;
				bounds.bottom = bounds.top+pNode->iHeight;

				FillBlock(pGlyph, &bounds, 0x0000);
				DrawGlyph(pNode,pGlyph,chr);

			}

			if(m_bItalic)
				width=BltItalicChar(pNode, pGlyph, pDst, XOffset, pClipRect, chr);
			else
				width=BltChar(pNode, pGlyph, pDst, XOffset, pClipRect, chr);

			XOffset+=width;

		}
		else
		{
			GetCharSize( &width, &height, chr );
			XOffset+=width;
		}
	}
}


void D3FFont::DrawStringToTexture(Text *pText, CacheEntry *pEntry)
{

	int				width;
	int				lineIndex;
	int				polyIndex;
	int				xOffset;
	D3DLOCKED_RECT	srcRect;
	D3DLOCKED_RECT	dstRect;
	RECT			rect;
	HRESULT			rc;
	CachedLine *	pCurrentLine;
	LineOfText *	pLineOfText;
			
	lineIndex = 0;

	rc = s_pGlyphCache->pD3DTexture->LockRect(0,&srcRect,0,0);
	if(!s_pGlyphCache->pTree->pChildX)
	{
		pText->SearchTree(s_pGlyphCache->pTree, MaxWidth(), m_iHeight);
		if((m_iCharCount-32)>s_iGlyphSpace)
		{
			s_iGlyphSpace = m_iCharCount-32;
			s_ppGlyphNodes = (SurfaceNode**)realloc(s_ppGlyphNodes,s_iGlyphSpace*sizeof(SurfaceNode**));
		}
		memset(s_ppGlyphNodes,0,s_iGlyphSpace*sizeof(SurfaceNode**));
	}

	pLineOfText = pText->GetLine(lineIndex);

	while(pLineOfText->pStart)
	{
		pCurrentLine = &pEntry->pLines[lineIndex];
		pCurrentLine->pString=pLineOfText->pStart;
		pCurrentLine->fYPos = pLineOfText->fYPos;
			pCurrentLine->fXPos = pLineOfText->fXPos;

		if(pLineOfText->iCount == 0)
		{
			pCurrentLine->iLength=-1;
			lineIndex++;
			continue;
		}

		width=pCurrentLine->iWidth=pLineOfText->iWidth;
		pCurrentLine->iHeight=pLineOfText->iHeight;

		polyIndex=0;
		xOffset=0;
		while(width>0)
		{
			SurfaceNode *	pCurrentNode;

			pCurrentNode = pText->FindFreeNode(pEntry,width,pCurrentLine->iHeight);
			width-=pCurrentNode->iWidth;
			pCurrentLine->pPolyNodes[polyIndex] = pCurrentNode;
			polyIndex++;

			rect.top = pCurrentNode->iTop;
			rect.left = pCurrentNode->iLeft;
			rect.right = rect.left+pCurrentNode->iWidth;
			rect.bottom = rect.top+pCurrentNode->iHeight;

			rc = pCurrentNode->pSurface->pD3DTexture->LockRect(0,&dstRect,0,0);

			FillBlock( &dstRect, &rect, 0x0000);
			BltBlockToTexture( &srcRect, &dstRect, xOffset, &rect, pCurrentLine->pString);

			rc =pCurrentNode->pSurface->pD3DTexture->UnlockRect(0);
			if(FAILED(rc)) STOP((_T("Unable to unlock texture")));

			xOffset-=pCurrentNode->iWidth;
		}
		pCurrentLine->iLength=pLineOfText->iCount;
		pCurrentLine->pPolyNodes[polyIndex] = NULL;
		lineIndex++;
		pLineOfText = pText->GetLine(lineIndex);
	}
	
	rc = s_pGlyphCache->pD3DTexture->UnlockRect(0);
	if(FAILED(rc)) STOP((_T("Unable to unlock texture")));
}


void D3FFont::DrawUncachedText(Text *pText)
{
	int				width;
	int				height;
	int				lineIndex;
	float			xPosition;
	float			yPosition;
	int				texXOffset;
	int				texWidth;
	D3DLOCKED_RECT	srcRect;
	RECT			rect;
	HRESULT			rc;
	LineOfText *	pLineOfText;
		
	pText->SetTextRenderstates();
	pText->SetDefaultTexture();

	rc = s_pGlyphCache->pD3DTexture->LockRect(0,&srcRect,0,0);
	if(!s_pGlyphCache->pTree->pChildX)
	{
		pText->SearchTree(s_pGlyphCache->pTree, MaxWidth(), m_iHeight);
		if((m_iCharCount-32)>s_iGlyphSpace)
		{
			s_iGlyphSpace = m_iCharCount-32;
			s_ppGlyphNodes = (SurfaceNode**)realloc(s_ppGlyphNodes,s_iGlyphSpace*sizeof(SurfaceNode**));
		}
		memset(s_ppGlyphNodes,0,s_iGlyphSpace*sizeof(SurfaceNode**));
	}


	lineIndex = 0;
	pLineOfText = pText->GetLine(lineIndex);

	while(pLineOfText->pStart)
	{
		if(pLineOfText->iCount == 0)
		{
			lineIndex++;
			continue;
		}

		width=pLineOfText->iWidth;
		xPosition = pLineOfText->fXPos;
		yPosition = pLineOfText->fYPos;

		if( pLineOfText->iHeight > pText->GetScratchHeight() )
			height = pText->GetScratchHeight();
		else
			height = pLineOfText->iHeight;

		texXOffset = 0;
			
		while(((int)width)>0)
		{

			D3DLOCKED_RECT	d3dlr;
			SurfaceCache *pSC;
			
			texWidth = width>c_SurfaceWidth?c_SurfaceWidth:width;
			rect.top = 0;
			rect.left = 0;
			rect.bottom = height;
			rect.right = texWidth;

			
			pSC = pText->LockSurface(0,&d3dlr);
			FillBlock(&d3dlr, &rect, 0x0000);
			BltBlockToTexture(&srcRect, &d3dlr,texXOffset, &rect, pLineOfText->pStart);
			pText->UnlockSurface(pSC);
			rect.bottom = height;
			rect.right = texWidth;
			pText->RenderLineFromTexture(&rect, xPosition-(texXOffset*m_fScale), yPosition, m_fScale);

			texXOffset-=texWidth;
			width-=texWidth;
		}

		lineIndex++;
		pLineOfText = pText->GetLine(lineIndex);
	}

	rc = s_pGlyphCache->pD3DTexture->UnlockRect(0);
	pText->ResetTextRenderstates();
}