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

 @file         PVRTPrint3D.cpp
 @copyright    Copyright (c) Imagination Technologies Limited.
 @brief        Displays a text string using 3D polygons. Can be done in two ways:
               using a window defined by the user or writing straight on the
               screen.

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

/****************************************************************************
** Includes
****************************************************************************/
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>

#include "PVRTGlobal.h"
#include "PVRTFixedPoint.h"
#include "PVRTMatrix.h"
#include "PVRTTexture.h"
#include "PVRTPrint3D.h"
#include "PVRTUnicode.h"
#include "PVRTContext.h"
#include "PVRTMap.h"

/* Print3D texture data */
#include "PVRTPrint3DIMGLogo.h"
#include "PVRTPrint3DHelveticaBold.h"

static inline float PVRTMakeWhole(float f)
{
	return floorf(f + 0.5f);
}


/****************************************************************************
** Defines
****************************************************************************/
#define MAX_LETTERS				(5120)
#define MIN_CACHED_VTX			(0x1000)
#define MAX_CACHED_VTX			(0x00100000)
#define LINES_SPACING			(29.0f)
#define PVRPRINT3DVERSION		(1)

#if defined(_WIN32)
#define vsnprintf _vsnprintf
#endif

const PVRTuint32 PVRFONT_HEADER			= 0xFCFC0050;
const PVRTuint32 PVRFONT_CHARLIST		= 0xFCFC0051;
const PVRTuint32 PVRFONT_RECTS			= 0xFCFC0052;
const PVRTuint32 PVRFONT_METRICS		= 0xFCFC0053;
const PVRTuint32 PVRFONT_YOFFSET		= 0xFCFC0054;
const PVRTuint32 PVRFONT_KERNING		= 0xFCFC0055;

/****************************************************************************
** Constants
****************************************************************************/
static const unsigned int PVRTPRINT3D_INVALID_CHAR = 0xFDFDFDFD;

/****************************************************************************
** Auxiliary functions
****************************************************************************/
/*!***************************************************************************
@fn       		CharacterCompareFunc
@param[in]		pA
@param[in]		pB
@return			PVRTint32	
@brief      	Compares two characters for binary search.
*****************************************************************************/
PVRTint32 CPVRTPrint3D::CharacterCompareFunc(const void* pA, const void* pB)
{
	return (*(PVRTint32*)pA - *(PVRTint32*)pB);
}

/*!***************************************************************************
@fn       		KerningCompareFunc
@param[in]		pA
@param[in]		pB
@return			PVRTint32	
@brief      	Compares two kerning pairs for binary search.
*****************************************************************************/
PVRTint32 CPVRTPrint3D::KerningCompareFunc(const void* pA, const void* pB)
{
	KerningPair* pPairA = (KerningPair*)pA;
	KerningPair* pPairB = (KerningPair*)pB;

	if(pPairA->uiPair > pPairB->uiPair)		return 1;
	if(pPairA->uiPair < pPairB->uiPair)		return -1;

	return 0;
}

/****************************************************************************
** Class: CPVRTPrint3D
****************************************************************************/
/*****************************************************************************
 @fn       		CPVRTPrint3D
 @brief      	Init some values.
*****************************************************************************/
CPVRTPrint3D::CPVRTPrint3D() :	m_pAPI(NULL), m_uLogoToDisplay(ePVRTPrint3DLogoNone), m_pwFacesFont(NULL), m_pPrint3dVtx(NULL), m_bTexturesSet(false), m_pVtxCache(NULL), m_nVtxCache(0),
								m_nVtxCacheMax(0), m_bRotate(false), m_nCachedNumVerts(0), m_pwzPreviousString(NULL), m_pszPreviousString(NULL), m_fPrevScale(0.0f), m_fPrevX(0.0f),
								m_fPrevY(0.0f), m_uiPrevCol(0), m_pUVs(NULL), m_pKerningPairs(NULL), m_pCharMatrics(NULL), m_fTexW(0.0f), m_fTexH(0.0f), m_pRects(NULL), m_pYOffsets(NULL), 
								m_uiNextLineH(0), m_uiSpaceWidth(0), m_uiNumCharacters(0), m_uiNumKerningPairs(0), m_uiAscent(0), m_pszCharacterList(NULL), m_bHasMipmaps(false), 
								m_bUsingProjection(false)
{
	memset(m_fScreenScale, 0, sizeof(m_fScreenScale));
	memset(m_ui32ScreenDim, 0, sizeof(m_ui32ScreenDim));

	PVRTMatrixIdentity(m_mModelView);
	PVRTMatrixIdentity(m_mProj);

	m_pwzPreviousString = new wchar_t[MAX_LETTERS + 1];
	m_pszPreviousString = new char[MAX_LETTERS + 1];
	m_pwzPreviousString[0] = 0;
	m_pszPreviousString[0] = 0;

	m_eFilterMethod[eFilterProc_Min] = eFilter_Default;
	m_eFilterMethod[eFilterProc_Mag] = eFilter_Default;
	m_eFilterMethod[eFilterProc_Mip] = eFilter_MipDefault;
}

/*****************************************************************************
 @fn       		~CPVRTPrint3D
 @brief      	De-allocate the working memory
*****************************************************************************/
CPVRTPrint3D::~CPVRTPrint3D()
{
	delete [] m_pwzPreviousString;
	delete [] m_pszPreviousString;

	delete [] m_pszCharacterList;
	delete [] m_pYOffsets;
	delete [] m_pCharMatrics;
	delete [] m_pKerningPairs;
	delete [] m_pRects;
	delete [] m_pUVs;
}

/*!***************************************************************************
@fn       		ReadMetaBlock
@param[in]		pDataCursor
@return			bool	true if successful.
@brief      	Reads a single meta data block from the data file.
*****************************************************************************/
bool CPVRTPrint3D::ReadMetaBlock(const PVRTuint8** pDataCursor)
{
	SPVRTPrint3DHeader* header;

	unsigned int uiDataSize;

	MetaDataBlock block;
	if(!block.ReadFromPtr(pDataCursor))
	{
		return false;		// Must have been an error.
	}

	switch(block.u32Key)
	{
	case PVRFONT_HEADER:
		header = (SPVRTPrint3DHeader*)block.Data;
		if(header->uVersion != PVRTPRINT3D_VERSION)
		{
			return false;
		}
		// Copy options
		m_uiAscent			= header->wAscent;
		m_uiNextLineH		= header->wLineSpace;
		m_uiSpaceWidth		= header->uSpaceWidth;
		m_uiNumCharacters	= header->wNumCharacters & 0xFFFF;
		m_uiNumKerningPairs = header->wNumKerningPairs & 0xFFFF;	
		break;
	case PVRFONT_CHARLIST:
		uiDataSize = sizeof(PVRTuint32) * m_uiNumCharacters;
		_ASSERT(block.u32DataSize == uiDataSize);
		m_pszCharacterList = new PVRTuint32[m_uiNumCharacters];
		memcpy(m_pszCharacterList, block.Data, uiDataSize);
		break;
	case PVRFONT_YOFFSET:
		uiDataSize = sizeof(PVRTint32) * m_uiNumCharacters;
		_ASSERT(block.u32DataSize == uiDataSize);
		m_pYOffsets	= new PVRTint32[m_uiNumCharacters];
		memcpy(m_pYOffsets, block.Data, uiDataSize);
		break;
	case PVRFONT_METRICS:
		uiDataSize = sizeof(CharMetrics) * m_uiNumCharacters;
		_ASSERT(block.u32DataSize == uiDataSize);
		m_pCharMatrics = new CharMetrics[m_uiNumCharacters];
		memcpy(m_pCharMatrics, block.Data, uiDataSize);
		break;
	case PVRFONT_KERNING:
		uiDataSize = sizeof(KerningPair) * m_uiNumKerningPairs;
		_ASSERT(block.u32DataSize == uiDataSize);
		m_pKerningPairs = new KerningPair[m_uiNumKerningPairs];
		memcpy(m_pKerningPairs, block.Data, uiDataSize);
		break;
	case PVRFONT_RECTS:
		uiDataSize = sizeof(Rectanglei) * m_uiNumCharacters;
		_ASSERT(block.u32DataSize == uiDataSize);

		m_pRects = new Rectanglei[m_uiNumCharacters];
		memcpy(m_pRects, block.Data, uiDataSize);
		break;
	default:
		_ASSERT(!"Unhandled key!");
	}

	return true;
}

/*!***************************************************************************
@fn       		LoadFontData
@param[in]		texHeader
@param[in]		MetaDataMap
@return			bool	true if successful.
@brief      	Loads font data bundled with the texture file.
*****************************************************************************/
bool CPVRTPrint3D::LoadFontData( const PVRTextureHeaderV3* texHeader, CPVRTMap<PVRTuint32, CPVRTMap<PVRTuint32, MetaDataBlock> >& MetaDataMap )
{
	m_fTexW = (float)texHeader->u32Width;
	m_fTexH = (float)texHeader->u32Height;

	// Mipmap data is stored in the texture header data.
	m_bHasMipmaps = (texHeader->u32MIPMapCount > 1 ? true : false);
	if(m_bHasMipmaps)
	{
		m_eFilterMethod[eFilterProc_Min] = eFilter_Linear;
		m_eFilterMethod[eFilterProc_Mag] = eFilter_Linear;
		m_eFilterMethod[eFilterProc_Mip] = eFilter_Linear;
	}
	else
	{
		m_eFilterMethod[eFilterProc_Min] = eFilter_Linear;
		m_eFilterMethod[eFilterProc_Mag] = eFilter_Linear;
		m_eFilterMethod[eFilterProc_Mip] = eFilter_None;
	}


	// Header
	SPVRTPrint3DHeader* header = (SPVRTPrint3DHeader*)MetaDataMap[PVRTEX3_IDENT][PVRFONT_HEADER].Data;
	if(header->uVersion != PVRTPRINT3D_VERSION)
	{
		return false;
	}
	// Copy options
	m_uiAscent			= header->wAscent;
	m_uiNextLineH		= header->wLineSpace;
	m_uiSpaceWidth		= header->uSpaceWidth;
	m_uiNumCharacters	= header->wNumCharacters & 0xFFFF;
	m_uiNumKerningPairs = header->wNumKerningPairs & 0xFFFF;	

	// Char list
	m_pszCharacterList = new PVRTuint32[m_uiNumCharacters];
	memcpy(m_pszCharacterList, MetaDataMap[PVRTEX3_IDENT][PVRFONT_CHARLIST].Data, MetaDataMap[PVRTEX3_IDENT][PVRFONT_CHARLIST].u32DataSize);
	
	m_pYOffsets	= new PVRTint32[m_uiNumCharacters];
	memcpy(m_pYOffsets, MetaDataMap[PVRTEX3_IDENT][PVRFONT_YOFFSET].Data, MetaDataMap[PVRTEX3_IDENT][PVRFONT_YOFFSET].u32DataSize);

	m_pCharMatrics = new CharMetrics[m_uiNumCharacters];
	memcpy(m_pCharMatrics, MetaDataMap[PVRTEX3_IDENT][PVRFONT_METRICS].Data, MetaDataMap[PVRTEX3_IDENT][PVRFONT_METRICS].u32DataSize);
	
	m_pKerningPairs = new KerningPair[m_uiNumKerningPairs];
	memcpy(m_pKerningPairs, MetaDataMap[PVRTEX3_IDENT][PVRFONT_KERNING].Data, MetaDataMap[PVRTEX3_IDENT][PVRFONT_KERNING].u32DataSize);

	m_pRects = new Rectanglei[m_uiNumCharacters];
	memcpy(m_pRects, MetaDataMap[PVRTEX3_IDENT][PVRFONT_RECTS].Data, MetaDataMap[PVRTEX3_IDENT][PVRFONT_RECTS].u32DataSize);
	

	// Build UVs
	m_pUVs = new CharacterUV[m_uiNumCharacters];
	for(unsigned int uiChar = 0; uiChar < m_uiNumCharacters; uiChar++)
	{
		m_pUVs[uiChar].fUL = m_pRects[uiChar].nX / m_fTexW;
		m_pUVs[uiChar].fUR = m_pUVs[uiChar].fUL + m_pRects[uiChar].nW / m_fTexW;
		m_pUVs[uiChar].fVT = m_pRects[uiChar].nY / m_fTexH;
		m_pUVs[uiChar].fVB = m_pUVs[uiChar].fVT + m_pRects[uiChar].nH / m_fTexH;
	}	

	return true;
}

/*!***************************************************************************
@fn       		FindCharacter
@param[in]		character
@return			The character index, or PVRPRINT3D_INVALID_CHAR if not found.
@brief      	Finds a given character in the binary data and returns it's
				index.
*****************************************************************************/
PVRTuint32 CPVRTPrint3D::FindCharacter(PVRTuint32 character) const
{
	PVRTuint32* pItem = (PVRTuint32*)bsearch(&character, m_pszCharacterList, m_uiNumCharacters, sizeof(PVRTuint32), CharacterCompareFunc);
	if(!pItem)
		return PVRTPRINT3D_INVALID_CHAR;

	PVRTuint32 uiIdx = (PVRTuint32) (pItem - m_pszCharacterList);
	return uiIdx;
}

/*!***************************************************************************
@fn       		ApplyKerning
@param[in]		cA
@param[in]		cB
@param[out]		fOffset
@brief      	Calculates kerning offset.
*****************************************************************************/
void CPVRTPrint3D::ApplyKerning(const PVRTuint32 cA, const PVRTuint32 cB, float& fOffset) const
{	
	PVRTuint64 uiPairToSearch = ((PVRTuint64)cA << 32) | (PVRTuint64)cB;
	KerningPair* pItem = (KerningPair*)bsearch(&uiPairToSearch, m_pKerningPairs, m_uiNumKerningPairs, sizeof(KerningPair), KerningCompareFunc);
	if(pItem)
		fOffset += (float)pItem->iOffset;
}

/*!***************************************************************************
 @fn       			SetTextures
 @param[in]			pContext		Context
 @param[in]			dwScreenX		Screen resolution along X
 @param[in]			dwScreenY		Screen resolution along Y
 @param[in]			bRotate			Rotate print3D by 90 degrees
 @param[in]			bMakeCopy		This instance of Print3D creates a copy
									of it's data instead of sharing with previous
									contexts. Set this parameter if you require
									thread safety.	
 @return			PVR_SUCCESS or PVR_FAIL
 @brief      		Initialization and texture upload. Should be called only once
					for a given context.
*****************************************************************************/
EPVRTError CPVRTPrint3D::SetTextures(
	const SPVRTContext	* const pContext,
	const unsigned int	dwScreenX,
	const unsigned int	dwScreenY,
	const bool bRotate,
	const bool bMakeCopy)
{
	// Determine which set of textures to use depending on the screen resolution.
	const unsigned int uiShortestEdge = PVRT_MIN(dwScreenX, dwScreenY);
	const void* pData = NULL;

	if(uiShortestEdge >= 720)
	{
		pData = (void*)_helvbd_56_pvr;
	}
	else if(uiShortestEdge >= 640)
	{
		pData = (void*)_helvbd_46_pvr;
	}
	else
	{
		pData = (void*)_helvbd_36_pvr;
	}
	
	PVRT_UNREFERENCED_PARAMETER(_helvbd_36_pvr_size);
	PVRT_UNREFERENCED_PARAMETER(_helvbd_46_pvr_size);
	PVRT_UNREFERENCED_PARAMETER(_helvbd_56_pvr_size);

	return SetTextures(pContext, pData, dwScreenX, dwScreenY, bRotate, bMakeCopy);
}

/*!***************************************************************************
	@fn       		SetTextures
	@param[in]		pContext		Context
	@param[in]		pTexData		User-provided font texture
	@param[in]		uiDataSize		Size of the data provided
	@param[in]		dwScreenX		Screen resolution along X
	@param[in]		dwScreenY		Screen resolution along Y
	@param[in]		bRotate			Rotate print3D by 90 degrees
	@param[in]		bMakeCopy		This instance of Print3D creates a copy
									of it's data instead of sharing with previous
									contexts. Set this parameter if you require
									thread safety.	
	@return			PVR_SUCCESS or PVR_FAIL
	@brief      	Initialization and texture upload of user-provided font 
					data. Should be called only once for a Print3D object.
*****************************************************************************/
EPVRTError CPVRTPrint3D::SetTextures(
	const SPVRTContext	* const pContext,
	const void * const pTexData,
	const unsigned int	dwScreenX,
	const unsigned int	dwScreenY,
	const bool bRotate,
	const bool bMakeCopy)
{
#if !defined (DISABLE_PRINT3D)

	unsigned short	i;
	bool			bStatus;

	// Set the aspect ratio, so we can change it without updating textures or anything else
	float fX, fY;

	m_bRotate = bRotate;
	m_ui32ScreenDim[0] = bRotate ? dwScreenY : dwScreenX;
	m_ui32ScreenDim[1] = bRotate ? dwScreenX : dwScreenY;

	// Alter the X, Y resolutions if the screen isn't portrait.
	if(dwScreenX > dwScreenY)
	{
		fX = (float) dwScreenX;
		fY = (float) dwScreenY;
	}
	else
	{
		fX = (float) dwScreenY;
		fY = (float) dwScreenX;
	}

	m_fScreenScale[0] = (bRotate ? fY : fX) /640.0f;
	m_fScreenScale[1] = (bRotate ? fX : fY) /480.0f;

	// Check whether textures are already set up just in case
	if (m_bTexturesSet)
		return PVR_SUCCESS;

	// INDEX BUFFERS
	m_pwFacesFont = (unsigned short*)malloc(PVRTPRINT3D_MAX_RENDERABLE_LETTERS*2*3*sizeof(unsigned short));

	if(!m_pwFacesFont)
	{
		return PVR_FAIL;
	}

	// Vertex indices for letters
	for (i=0; i < PVRTPRINT3D_MAX_RENDERABLE_LETTERS; i++)
	{
		m_pwFacesFont[i*6+0] = 0+i*4;
		m_pwFacesFont[i*6+1] = 3+i*4;
		m_pwFacesFont[i*6+2] = 1+i*4;

		m_pwFacesFont[i*6+3] = 3+i*4;
		m_pwFacesFont[i*6+4] = 0+i*4;
		m_pwFacesFont[i*6+5] = 2+i*4;
	}


	if(!APIInit(pContext, bMakeCopy))
	{
		return PVR_FAIL;
	}
	/*
		This is the texture with the fonts.
	*/
	PVRTextureHeaderV3 header;
	CPVRTMap<PVRTuint32, CPVRTMap<PVRTuint32, MetaDataBlock> > MetaDataMap;
	bStatus = APIUpLoadTexture((unsigned char *)pTexData, &header, MetaDataMap);

	if (!bStatus)
	{
		return PVR_FAIL;
	}
	/*
		This is the associated font data with the default font
	*/
	bStatus = LoadFontData(&header, MetaDataMap);
	
	bStatus = APIUpLoadIcons(reinterpret_cast<const PVRTuint8* const>(PVRTPrint3DIMGLogo), reinterpret_cast<const PVRTuint8* const>(PVRTPrint3DPowerVRLogo));

	if (!bStatus) return PVR_FAIL;

	m_nVtxCacheMax = MIN_CACHED_VTX;
	m_pVtxCache = (SPVRTPrint3DAPIVertex*)malloc(m_nVtxCacheMax * sizeof(*m_pVtxCache));
	m_nVtxCache = 0;

	if(!m_pVtxCache)
	{
		return PVR_FAIL;
	}

	// Everything is OK
	m_bTexturesSet = true;

	// Return Success
	return PVR_SUCCESS;

#else
	return PVR_SUCCESS;
#endif
}

/*!***************************************************************************
@fn       		Print3D
@param[in]		fPosX		X Position
@param[in]		fPosY		Y Position
@param[in]		fScale		Text scale
@param[in]		Colour		ARGB colour
@param[in]		UTF32		Array of UTF32 characters
@param[in]		bUpdate		Whether to update the vertices
@return			EPVRTError	Success of failure
@brief      	Takes an array of UTF32 characters and generates the required mesh.
*****************************************************************************/
EPVRTError CPVRTPrint3D::Print3D(float fPosX, float fPosY, const float fScale, unsigned int Colour, const CPVRTArray<PVRTuint32>& UTF32, bool bUpdate)
{
	// No textures! so... no window
	if (!m_bTexturesSet)
	{
		PVRTErrorOutputDebug("DisplayWindow : You must call CPVRTPrint3D::SetTextures(...) before using this function.\n");
		return PVR_FAIL;
	}

	// nothing to be drawn
	if(UTF32.GetSize() == 0)
		return PVR_FAIL;

	// Adjust input parameters
	if(!m_bUsingProjection)
	{
		fPosX =  (float)((int)(fPosX * (640.0f/100.0f)));
		fPosY = -(float)((int)(fPosY * (480.0f/100.0f)));
	}

	// Create Vertex Buffer (only if it doesn't exist)
	if(m_pPrint3dVtx == 0)
	{
		m_pPrint3dVtx = (SPVRTPrint3DAPIVertex*)malloc(MAX_LETTERS*4*sizeof(SPVRTPrint3DAPIVertex));

		if(!m_pPrint3dVtx)
			return PVR_FAIL;
	}

	// Fill up our buffer
	if(bUpdate)
		m_nCachedNumVerts = UpdateLine(0.0f, fPosX, fPosY, fScale, Colour, UTF32, m_pPrint3dVtx);

	// Draw the text
	if(!DrawLine(m_pPrint3dVtx, m_nCachedNumVerts))
		return PVR_FAIL;

	return PVR_SUCCESS;
}

/*!***************************************************************************
 @fn       			Print3D
 @param[in]			fPosX		Position of the text along X
 @param[in]			fPosY		Position of the text along Y
 @param[in]			fScale		Scale of the text
 @param[in]			Colour		Colour of the text
 @param[in]			pszFormat	Format string for the text
 @return			PVR_SUCCESS or PVR_FAIL
 @brief      		Display wide-char 3D text on screen.
					CPVRTPrint3D::SetTextures(...) must have been called
					beforehand.
					This function accepts formatting in the printf way.
*****************************************************************************/
EPVRTError CPVRTPrint3D::Print3D(float fPosX, float fPosY, const float fScale, unsigned int Colour, const wchar_t * const pszFormat, ...)
{
#ifdef DISABLE_PRINT3D
	return PVR_SUCCESS;
#endif

	static wchar_t s_Text[MAX_LETTERS+1] = {0};

	/*
		Unfortunately only Windows seems to properly support non-ASCII characters formatted in
		vswprintf.
	*/
#if defined(_WIN32) && !defined(UNDER_CE)
	va_list		args;
	// Reading the arguments to create our Text string
	va_start(args, pszFormat);
	vswprintf(s_Text, MAX_LETTERS+1, pszFormat, args);
	va_end(args);
#else
	wcscpy(s_Text, pszFormat);
#endif

	bool bUpdate = false;

	// Optimisation to check that the strings are actually different.
	if(wcscmp(s_Text, m_pwzPreviousString) != 0 || m_fPrevX != fPosX || m_fPrevY != fPosY || m_fPrevScale != fScale || m_uiPrevCol != Colour)
	{
		// Copy strings
		wcscpy(m_pwzPreviousString, s_Text);
		m_fPrevX = fPosX;
		m_fPrevY = fPosY;
		m_fPrevScale = fScale;
		m_uiPrevCol  = Colour;
		
		m_CachedUTF32.Clear();
#if PVRTSIZEOFWCHAR == 2			// 2 byte wchar.
		PVRTUnicodeUTF16ToUTF32((PVRTuint16*)s_Text, m_CachedUTF32);
#elif PVRTSIZEOFWCHAR == 4			// 4 byte wchar (POSIX)
		unsigned int uiC = 0;
		PVRTuint32* pUTF32 = (PVRTuint32*)s_Text;
		while(*pUTF32 && uiC < MAX_LETTERS)
		{
			m_CachedUTF32.Append(*pUTF32++);
			uiC++;
		}
#else
		return PVR_FAIL;
#endif

		bUpdate = true;
	}

	// Print
	return Print3D(fPosX, fPosY, fScale, Colour, m_CachedUTF32, bUpdate);
}

/*!***************************************************************************
 @fn       			PVRTPrint3D
 @param[in]			fPosX		Position of the text along X
 @param[in]			fPosY		Position of the text along Y
 @param[in]			fScale		Scale of the text
 @param[in]			Colour		Colour of the text
 @param[in]			pszFormat	Format string for the text
 @return			PVR_SUCCESS or PVR_FAIL
 @brief      		Display 3D text on screen.
					No window needs to be allocated to use this function.
					However, CPVRTPrint3D::SetTextures(...) must have been called
					beforehand.
					This function accepts formatting in the printf way.
*****************************************************************************/
EPVRTError CPVRTPrint3D::Print3D(float fPosX, float fPosY, const float fScale, unsigned int Colour, const char * const pszFormat, ...)
{
#ifdef DISABLE_PRINT3D
	return PVR_SUCCESS;
#endif

	va_list		args;
	static char	s_Text[MAX_LETTERS+1] = {0};
	
	// Reading the arguments to create our Text string
	va_start(args, pszFormat);
	vsnprintf(s_Text, MAX_LETTERS+1, pszFormat, args);
	va_end(args);

	bool bUpdate = false;

	// Optimisation to check that the strings are actually different.
	if(strcmp(s_Text, m_pszPreviousString) != 0 || m_fPrevX != fPosX || m_fPrevY != fPosY || m_fPrevScale != fScale || m_uiPrevCol != Colour)
	{
		// Copy strings
		strcpy (m_pszPreviousString, s_Text);
		m_fPrevX = fPosX;
		m_fPrevY = fPosY;
		m_fPrevScale = fScale;
		m_uiPrevCol  = Colour;

		// Convert from UTF8 to UTF32
		m_CachedUTF32.Clear();
		PVRTUnicodeUTF8ToUTF32((const PVRTuint8*)s_Text, m_CachedUTF32);

		bUpdate = true;
	}

	// Print
	return Print3D(fPosX, fPosY, fScale, Colour, m_CachedUTF32, bUpdate);
}

/*!***************************************************************************
 @fn       			DisplayDefaultTitle
 @param[in]			sTitle				Title to display
 @param[in]			sDescription		Description to display
 @param[in]			uDisplayLogo		1 = Display the logo
 @return			PVR_SUCCESS or PVR_FAIL
 @brief      		Creates a default title with predefined position and colours.
					It displays as well company logos when requested:
					0 = No logo
					1 = PowerVR logo
					2 = Img Tech logo
*****************************************************************************/
EPVRTError CPVRTPrint3D::DisplayDefaultTitle(const char * const pszTitle, const char * const pszDescription, const unsigned int uDisplayLogo)
{
	EPVRTError eRet = PVR_SUCCESS;

#if !defined (DISABLE_PRINT3D)

	// Display Title
	if(pszTitle)
	{
		if(Print3D(0.0f, -1.0f, 1.0f,  PVRTRGBA(255, 255, 255, 255), pszTitle) != PVR_SUCCESS)
			eRet = PVR_FAIL;
	}
	
	float fYVal;
	if(m_bRotate)
		fYVal = m_fScreenScale[0] * 480.0f;
	else
		fYVal = m_fScreenScale[1] * 480.0f;

	// Display Description
	if(pszDescription)
	{
        float fY;
		float a = 320.0f/fYVal;
		fY = m_uiNextLineH / (480.0f/100.0f) * a;
		
		if(Print3D(0.0f, fY, 0.8f,  PVRTRGBA(255, 255, 255, 255), pszDescription) != PVR_SUCCESS)
			eRet = PVR_FAIL;
	}

	m_uLogoToDisplay = uDisplayLogo;

#endif

	return eRet;
}

/*!***************************************************************************
 @fn       			MeasureText
 @param[out]		pfWidth				Width of the string in pixels
 @param[out]		pfHeight			Height of the string in pixels
 @param[in]			fFontSize			Font size
 @param[in]			sString				String to take the size of
 @brief      		Returns the size of a string in pixels.
*****************************************************************************/
void CPVRTPrint3D::MeasureText(
	float		* const pfWidth,
	float		* const pfHeight,
	float				fScale,
	const CPVRTArray<PVRTuint32>& utf32)
{
#if !defined (DISABLE_PRINT3D)
	if(utf32.GetSize() == 0) {
		if(pfWidth)
			*pfWidth = 0;
		if(pfHeight)
			*pfHeight = 0;
		return;
	}

	float fLength			= 0;
	float fMaxLength		= -1.0f;
	float fMaxHeight		= (float)m_uiNextLineH;
	PVRTuint32 txNextChar	= 0;
	PVRTuint32 uiIdx;
	for(PVRTuint32 uiIndex = 0; uiIndex < utf32.GetSize(); uiIndex++)
	{
		if(utf32[uiIndex] == 0x0D || utf32[uiIndex] == 0x0A)
		{
			if(fLength > fMaxLength)
				fMaxLength = fLength;

			fLength = 0;
			fMaxHeight += (float)m_uiNextLineH;
		}
		uiIdx = FindCharacter(utf32[uiIndex]);
		if(uiIdx == PVRTPRINT3D_INVALID_CHAR)		// No character found. Add a space.
		{
			fLength += m_uiSpaceWidth;
			continue;
		}

		txNextChar = utf32[uiIndex + 1];
		float fKernOffset = 0;
		ApplyKerning(utf32[uiIndex], txNextChar, fKernOffset);

		fLength += m_pCharMatrics[uiIdx].nAdv + fKernOffset;		// Add on this characters width
	}

	if(fMaxLength < 0.0f)		// Obviously no new line.
		fMaxLength = fLength;

	if(pfWidth)
		*pfWidth = fMaxLength * fScale;
	if(pfHeight)
		*pfHeight = fMaxHeight * fScale;
#endif
}

/*!***************************************************************************
 @fn       			GetSize
 @param[out]		pfWidth				Width of the string in pixels
 @param[out]		pfHeight			Height of the string in pixels
 @param[in]			pszUTF8				UTF8 string to take the size of
 @brief      		Returns the size of a string in pixels.
*****************************************************************************/
void CPVRTPrint3D::MeasureText(
	float		* const pfWidth,
	float		* const pfHeight,
	float				fScale,
	const char	* const pszUTF8)
{
	m_CachedUTF32.Clear();
	PVRTUnicodeUTF8ToUTF32((PVRTuint8*)pszUTF8, m_CachedUTF32);
	MeasureText(pfWidth,pfHeight,fScale,m_CachedUTF32);
}

/*!***************************************************************************
 @fn       			MeasureText
 @param[out]		pfWidth		Width of the string in pixels
 @param[out]		pfHeight	Height of the string in pixels
 @param[in]			pszUnicode	Wide character string to take the length of.
 @brief      		Returns the size of a string in pixels.
*****************************************************************************/
void CPVRTPrint3D::MeasureText(
	float		* const pfWidth,
	float		* const pfHeight,
	float				fScale,
	const wchar_t* const pszUnicode)
{
	_ASSERT(pszUnicode);
	m_CachedUTF32.Clear();

#if PVRTSIZEOFWCHAR == 2			// 2 byte wchar.
	PVRTUnicodeUTF16ToUTF32((PVRTuint16*)pszUnicode, m_CachedUTF32);
#else								// 4 byte wchar (POSIX)
	unsigned int uiC = 0;
	PVRTuint32* pUTF32 = (PVRTuint32*)pszUnicode;
	while(*pUTF32 && uiC < MAX_LETTERS)
	{
		m_CachedUTF32.Append(*pUTF32++);
		uiC++;
	}
#endif
	
	MeasureText(pfWidth,pfHeight,fScale,m_CachedUTF32);
}

/*!***************************************************************************
 @fn       			GetAspectRatio
 @param[out]		dwScreenX		Screen resolution X
 @param[out]		dwScreenY		Screen resolution Y
 @brief      		Returns the current resolution used by Print3D
*****************************************************************************/
void CPVRTPrint3D::GetAspectRatio(unsigned int *dwScreenX, unsigned int *dwScreenY)
{
#if !defined (DISABLE_PRINT3D)

	*dwScreenX = (int)(640.0f * m_fScreenScale[0]);
	*dwScreenY = (int)(480.0f * m_fScreenScale[1]);
#endif
}

/*************************************************************
*					 PRIVATE FUNCTIONS						 *
**************************************************************/

/*!***************************************************************************
 @brief             Update a single line
 @param[in]			fZPos
 @param[in]			XPos
 @param[in]			YPos
 @param[in]			fScale
 @param[in]			Colour
 @param[in]			Text
 @param[in]			pVertices
 @return            Number of vertices affected
*****************************************************************************/
unsigned int CPVRTPrint3D::UpdateLine(const float fZPos, float XPos, float YPos, const float fScale, const unsigned int Colour, const CPVRTArray<PVRTuint32>& Text, SPVRTPrint3DAPIVertex * const pVertices)
{
	/* Nothing to update */
	if (Text.GetSize() == 0) 
		return 0;

	if(!m_bUsingProjection)
	{
		XPos *= ((float)m_ui32ScreenDim[0] / 640.0f);
		YPos *= ((float)m_ui32ScreenDim[1] / 480.0f);
	}

	YPos -= m_uiAscent * fScale;
	
	YPos = PVRTMakeWhole(YPos);

	float fPreXPos	= XPos;		// The original offset (after screen scale modification) of the X coordinate.

	float		fKernOffset;
	float		fAOff;
	float		fYOffset;
	unsigned int VertexCount = 0;
	PVRTint32 NextChar;

	unsigned int uiNumCharsInString = Text.GetSize();
	for(unsigned int uiIndex = 0; uiIndex < uiNumCharsInString; uiIndex++)
	{
		if(uiIndex > MAX_LETTERS) 
			break;

		// Newline
		if(Text[uiIndex] == 0x0A)
		{
			XPos = fPreXPos;
			YPos -= PVRTMakeWhole(m_uiNextLineH * fScale);
			continue;
		}

		// Get the character
		PVRTuint32 uiIdx = FindCharacter(Text[uiIndex]);

		// Not found. Add a space.
		if(uiIdx == PVRTPRINT3D_INVALID_CHAR)		// No character found. Add a space.
		{
			XPos += PVRTMakeWhole(m_uiSpaceWidth * fScale);
			continue;
		}

		fKernOffset = 0;
		fYOffset	= m_pYOffsets[uiIdx] * fScale;
		fAOff		= PVRTMakeWhole(m_pCharMatrics[uiIdx].nXOff * fScale);					// The A offset. Could include overhang or underhang.
		if(uiIndex < uiNumCharsInString - 1)
		{
			NextChar = Text[uiIndex + 1];
			ApplyKerning(Text[uiIndex], NextChar, fKernOffset);
		}

		/* Filling vertex data */
		pVertices[VertexCount+0].sx		= f2vt(XPos + fAOff);
		pVertices[VertexCount+0].sy		= f2vt(YPos + fYOffset);
		pVertices[VertexCount+0].sz		= f2vt(fZPos);
		pVertices[VertexCount+0].rhw	= f2vt(1.0f);
		pVertices[VertexCount+0].tu		= f2vt(m_pUVs[uiIdx].fUL);
		pVertices[VertexCount+0].tv		= f2vt(m_pUVs[uiIdx].fVT);

		pVertices[VertexCount+1].sx		= f2vt(XPos + fAOff + PVRTMakeWhole(m_pRects[uiIdx].nW * fScale));
		pVertices[VertexCount+1].sy		= f2vt(YPos + fYOffset);
		pVertices[VertexCount+1].sz		= f2vt(fZPos);
		pVertices[VertexCount+1].rhw	= f2vt(1.0f);
		pVertices[VertexCount+1].tu		= f2vt(m_pUVs[uiIdx].fUR);
		pVertices[VertexCount+1].tv		= f2vt(m_pUVs[uiIdx].fVT);

		pVertices[VertexCount+2].sx		= f2vt(XPos + fAOff);
		pVertices[VertexCount+2].sy		= f2vt(YPos + fYOffset - PVRTMakeWhole(m_pRects[uiIdx].nH * fScale));
		pVertices[VertexCount+2].sz		= f2vt(fZPos);
		pVertices[VertexCount+2].rhw	= f2vt(1.0f);
		pVertices[VertexCount+2].tu		= f2vt(m_pUVs[uiIdx].fUL);
		pVertices[VertexCount+2].tv		= f2vt(m_pUVs[uiIdx].fVB);

		pVertices[VertexCount+3].sx		= f2vt(XPos + fAOff + PVRTMakeWhole(m_pRects[uiIdx].nW * fScale));
		pVertices[VertexCount+3].sy		= f2vt(YPos + fYOffset - PVRTMakeWhole(m_pRects[uiIdx].nH * fScale));
		pVertices[VertexCount+3].sz		= f2vt(fZPos);
		pVertices[VertexCount+3].rhw	= f2vt(1.0f);
		pVertices[VertexCount+3].tu		= f2vt(m_pUVs[uiIdx].fUR);
		pVertices[VertexCount+3].tv		= f2vt(m_pUVs[uiIdx].fVB);

		pVertices[VertexCount+0].color	= Colour;
		pVertices[VertexCount+1].color	= Colour;
		pVertices[VertexCount+2].color	= Colour;
		pVertices[VertexCount+3].color	= Colour;

		XPos = XPos + PVRTMakeWhole((m_pCharMatrics[uiIdx].nAdv + fKernOffset) * fScale);		// Add on this characters width
		VertexCount += 4;
	}

	return VertexCount;
}

/*!***************************************************************************
 @fn       			DrawLineUP
 @return			true or false
 @brief      		Draw a single line of text.
*****************************************************************************/
bool CPVRTPrint3D::DrawLine(SPVRTPrint3DAPIVertex *pVtx, unsigned int nVertices)
{
	if(!nVertices)
		return true;

	_ASSERT((nVertices % 4) == 0);
	_ASSERT((nVertices/4) < MAX_LETTERS);

	while(m_nVtxCache + (int)nVertices > m_nVtxCacheMax) {
		if(m_nVtxCache + nVertices > MAX_CACHED_VTX) {
			_RPT1(_CRT_WARN, "Print3D: Out of space to cache text! (More than %d vertices!)\n", MAX_CACHED_VTX);
			return false;
		}

		m_nVtxCacheMax	= PVRT_MIN(m_nVtxCacheMax * 2, MAX_CACHED_VTX);
		SPVRTPrint3DAPIVertex* pTmp = (SPVRTPrint3DAPIVertex*)realloc(m_pVtxCache, m_nVtxCacheMax * sizeof(*m_pVtxCache));

		_ASSERT(pTmp);
		if(!pTmp)
		{
			free(m_pVtxCache);
			m_pVtxCache = 0;
			return false; // Failed to re-allocate data
		}

		m_pVtxCache = pTmp;
		
		_RPT1(_CRT_WARN, "Print3D: TextCache increased to %d vertices.\n", m_nVtxCacheMax);
	}

	memcpy(&m_pVtxCache[m_nVtxCache], pVtx, nVertices * sizeof(*pVtx));
	m_nVtxCache += nVertices;
	return true;
}

/*!***************************************************************************
 @fn       			SetProjection
 @brief      		Sets projection matrix.
*****************************************************************************/
void CPVRTPrint3D::SetProjection(const PVRTMat4& mProj)
{
	m_mProj				= mProj;
	m_bUsingProjection	= true;
}

/*!***************************************************************************
 @fn       			SetModelView
 @brief      		Sets model view matrix.
*****************************************************************************/
void CPVRTPrint3D::SetModelView(const PVRTMat4& mModelView)
{
	m_mModelView = mModelView;
}

/*!***************************************************************************
 @fn       		SetFiltering
 @param[in]		eFilter				The method of texture filtering
 @brief      	Sets the method of texture filtering for the font texture.
					Print3D will attempt to pick the best method by default
					but this method allows the user to override this.
*****************************************************************************/
void CPVRTPrint3D::SetFiltering(ETextureFilter eMin, ETextureFilter eMag, ETextureFilter eMip)
{
	if(eMin == eFilter_None) eMin = eFilter_Default;		// Illegal value
	if(eMag == eFilter_None) eMag = eFilter_Default;		// Illegal value

	m_eFilterMethod[eFilterProc_Min] = eMin;
	m_eFilterMethod[eFilterProc_Mag] = eMag;
	m_eFilterMethod[eFilterProc_Mip] = eMip;
}

/*!***************************************************************************
 @fn       		GetFontAscent
 @return		unsigned int	The ascent.
 @brief      	Returns the 'ascent' of the font. This is typically the 
				height from the baseline of the larget glyph in the set.
*****************************************************************************/
unsigned int CPVRTPrint3D::GetFontAscent()
{
	return m_uiAscent;
}

/*!***************************************************************************
 @fn       		GetFontLineSpacing
 @return		unsigned int	The line spacing.
 @brief      	Returns the default line spacing (i.e baseline to baseline) 
				for the font.
*****************************************************************************/
unsigned int CPVRTPrint3D::GetFontLineSpacing()
{
	return m_uiNextLineH;
}

/****************************************************************************
** Local code
****************************************************************************/

/*****************************************************************************
 End of file (PVRTPrint3D.cpp)
*****************************************************************************/