/*==LICENSE==*

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

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

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

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

Additional permissions under GNU GPL version 3 section 7

If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.

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

*==LICENSE==*/
///////////////////////////////////////////////////////////////////////////////
//																			 //
//	plFont Class Header														 //
//	Seems like we've come full circle again. This is our generic Plasma		 //
//	bitmap font class/format. Quick list of things it supports, or needs to: //
//		- Antialiasing, either in the font def or at rendertime				 //
//		- Doublebyte character sets											 //
//		- Platform independence, of course									 //
//		- Render to reasonably arbitrary mipmap								 //
//		- Character-level kerning, both before and after, as well as		 //
//		  negative kerning (for ligatures)									 //
//																			 //
//	Cyan, Inc.																 //
//																			 //
//// Version History //////////////////////////////////////////////////////////
//																			 //
//	3.4.2003 mcn - Created.													 //
//	4.3.2003 mcn - Updated. Casting a char to a UInt16 sign-extends it if	 //
//				   the char is > 128, but casting it to an UInt8 first works.//
//				   Ugly as sin, but hey, so are you.						 //
//																			 //
///////////////////////////////////////////////////////////////////////////////

#include "hsTypes.h"
#include "hsStlUtils.h"
#include "plFont.h"

#include "plMipmap.h"
#include "hsResMgr.h"


//// plCharacter Stuff ////////////////////////////////////////////////////////

plFont::plCharacter::plCharacter()
{
	fBitmapOff = 0;
	fHeight = 0;
	fBaseline = 0;
	fLeftKern = fRightKern = 0.f;
}

void	plFont::plCharacter::Read( hsStream *s )
{
	// Rocket science here...
	s->ReadSwap( &fBitmapOff );
	s->ReadSwap( &fHeight );
	s->ReadSwap( &fBaseline );
	s->ReadSwap( &fLeftKern );
	s->ReadSwap( &fRightKern );
}

void	plFont::plCharacter::Write( hsStream *s )
{
	s->WriteSwap( fBitmapOff );
	s->WriteSwap( fHeight );
	s->WriteSwap( fBaseline );
	s->WriteSwap( fLeftKern );
	s->WriteSwap( fRightKern );
}

//// Constructor/Read/Write/Destructor/etc ////////////////////////////////////

plFont::plFont()
{
	IClear( true );
}

plFont::~plFont()
{
	IClear();
}

void	plFont::IClear( bool onConstruct )
{
	if( !onConstruct )
		delete [] fBMapData;

	memset( fFace, 0, sizeof( fFace ) );
	fSize = 0;
	fFlags = 0;

	fWidth = fHeight = 0;
	fBPP = 0;
	fBMapData = nil;
	fFirstChar = 0;
	fMaxCharHeight = 0;
	fCharacters.Reset();

	fRenderInfo.fFlags = 0;
	fRenderInfo.fX = fRenderInfo.fY = fRenderInfo.fNumCols = 0;
	fRenderInfo.fMaxWidth = fRenderInfo.fMaxHeight = 0;
	fRenderInfo.fDestPtr = nil;
	fRenderInfo.fDestStride = 0;
	fRenderInfo.fColor = 0;
	fRenderInfo.fMipmap = nil;
	fRenderInfo.fRenderFunc = nil;
	fRenderInfo.fVolatileStringPtr = nil;
	fRenderInfo.fFirstLineIndent = 0;
	fRenderInfo.fLineSpacing = 0;
}

void	plFont::SetFace( const char *face )
{
	strncpy( fFace, face, sizeof( fFace ) );
}

void	plFont::SetSize( UInt8 size )
{
	fSize = size;
}

void	plFont::Read( hsStream *s, hsResMgr *mgr )
{
	hsKeyedObject::Read( s, mgr );
	ReadRaw( s );
}

void	plFont::Write( hsStream *s, hsResMgr *mgr )
{
	hsKeyedObject::Write( s, mgr );
	WriteRaw( s );
}

///////////////////////////////////////////////////////////////////////////////
//// Rendering ////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

void	plFont::SetRenderColor( UInt32 color )
{
	fRenderInfo.fColor = color;
}

void	plFont::SetRenderClipRect( Int16 x, Int16 y, Int16 width, Int16 height )
{
	fRenderInfo.fClipRect.Set( x, y, width, height );
}

void	plFont::SetRenderClipping( Int16 x, Int16 y, Int16 width, Int16 height )
{
	SetRenderFlag( kRenderWrap, false );
	SetRenderFlag( kRenderClip, true );
	SetRenderClipRect( x, y, width, height );
}

void	plFont::SetRenderWrapping( Int16 x, Int16 y, Int16 width, Int16 height )
{
	SetRenderFlag( kRenderWrap, true );
	SetRenderFlag( kRenderClip, false );
	SetRenderClipRect( x, y, width, height );
}

void	plFont::ICalcFontAscent( void )
{
	UInt32	i;


	// Hack for now, only calc the ascent for characters in the 127 character ASCII range
	for( i = 0, fFontAscent = 0, fFontDescent = 0, fMaxCharHeight = 0; i < fCharacters.GetCount(); i++ )
	{
		if( i + fFirstChar < 128 && fFontAscent < fCharacters[ i ].fBaseline )
			fFontAscent = fCharacters[ i ].fBaseline;

		Int32 descent = fCharacters[ i ].fHeight - fCharacters[ i ].fBaseline;
		if( fFontDescent < descent )
			fFontDescent = descent;

		if( fMaxCharHeight < fCharacters[ i ].fHeight )
			fMaxCharHeight = fCharacters[ i ].fHeight;
	}
}

//// IIsWordBreaker //////////////////////////////////////////////////////////
//	Returns whether the given character is one that can break a line

static inline bool	IIsWordBreaker( const char c )
{
	return ( strchr( " \t,.;\n", c ) != nil ) ? true : false;
}

//// IIsDrawableWordBreak /////////////////////////////////////////////////////
// Returns whether the given character is a line breaker that has to be drawn
// (non whitespace)

static inline bool	IIsDrawableWordBreak( const char c )
{
	return ( strchr( ",.;", c ) != nil ) ? true : false;
}

//// RenderString /////////////////////////////////////////////////////////////
//	The base render function. Additional options are specified externally,
//	so that their effects can be cached for optimization

void	plFont::RenderString( plMipmap *mip, UInt16 x, UInt16 y, const char *string, UInt16 *lastX, UInt16 *lastY )
{
	// convert the char string to a wchar_t string
	wchar_t *wideString = hsStringToWString(string);
	RenderString(mip,x,y,wideString,lastX,lastY);
	delete [] wideString;
}


void	plFont::RenderString( plMipmap *mip, UInt16 x, UInt16 y, const wchar_t *string, UInt16 *lastX, UInt16 *lastY )
{
	if( mip->IsCompressed() )
	{
		hsAssert( false, "Unable to render string to compressed mipmap" );
		return;
	}

	IRenderString( mip, x, y, string, false );
	if( lastX != nil )
		*lastX = fRenderInfo.fLastX;
	if( lastY != nil )
		*lastY = fRenderInfo.fLastY;
}

void	plFont::IRenderString( plMipmap *mip, UInt16 x, UInt16 y, const wchar_t *string, hsBool justCalc )
{
	fRenderInfo.fMipmap = mip;
	fRenderInfo.fX = x;
	fRenderInfo.fY = y;
	fRenderInfo.fNumCols = (Int16)(( fBPP <= 8 ) ? ( ( fWidth * fBPP ) >> 3 ) : 0);
	fRenderInfo.fFloatWidth = (hsScalar)fWidth;
	fRenderInfo.fFarthestX = x;
	fRenderInfo.fMaxAscent = 0;
	fRenderInfo.fVolatileStringPtr = string;

	switch( fRenderInfo.fFlags & kRenderJustYMask )
	{
		case kRenderJustYTop: 
			fRenderInfo.fY += (Int16)fFontAscent; 
			break;
		case kRenderJustYBottom:
			if( fRenderInfo.fFlags & ( kRenderClip | kRenderWrap ) )
				fRenderInfo.fY = (Int16)(fRenderInfo.fClipRect.GetBottom() - 1 - fMaxCharHeight + fFontAscent);
			else
				fRenderInfo.fY = (Int16)(mip->GetHeight() - 1 - fMaxCharHeight + fFontAscent);
			break;
		case kRenderJustYCenter:
			fRenderInfo.fY = (Int16)(( fRenderInfo.fFlags & ( kRenderClip | kRenderWrap ) ) ? fRenderInfo.fClipRect.GetBottom() - 1 : mip->GetHeight() - 1);
			fRenderInfo.fY = (Int16)(( fRenderInfo.fY - fMaxCharHeight ) >> 1);
			fRenderInfo.fY += (Int16)fFontAscent;
			break;
		default:	// Just the baseline
			;
	}

	if( justCalc )
	{
		plCharacter &ch = fCharacters[ (UInt16)string[ 0 ] - fFirstChar ];
		fRenderInfo.fX = fRenderInfo.fFarthestX = x - (Int16)ch.fLeftKern;
		if( fRenderInfo.fX < 0 )
			fRenderInfo.fX = 0;
	}
	else
	{
		switch( fRenderInfo.fFlags & kRenderJustXMask )
		{
			case kRenderJustXLeft:
				// Default
				break;
			case kRenderJustXForceLeft:
				{
//					plCharacter &ch = fCharacters[ (UInt16)(UInt8)string[ 0 ] - fFirstChar ];
//					Int32 newX = x - (Int16)ch.fLeftKern;
//					if( newX < 0 )
//						newX = 0;
//					fRenderInfo.fX = fRenderInfo.fFarthestX = newX;
				}
				break;
			case kRenderJustXCenter:
				{
					UInt16 right = (UInt16)(( fRenderInfo.fFlags & ( kRenderClip | kRenderWrap ) ) ? fRenderInfo.fClipRect.GetRight() : mip->GetWidth());
//					UInt16 width = CalcStringWidth( string );
					fRenderInfo.fX = fRenderInfo.fFarthestX = ( ( x + right ) >> 1 );// - ( width >> 1 );
				}
				break;
			case kRenderJustXRight:
				{
					UInt16 width = 0, right = (UInt16)(( fRenderInfo.fFlags & ( kRenderClip | kRenderWrap ) ) ? fRenderInfo.fClipRect.GetRight() : mip->GetWidth());
/*					if( fRenderInfo.fFlags & kRenderClip )
					{
						fRenderInfo.fFlags &= ~kRenderClip;
						width = CalcStringWidth( string );
						fRenderInfo.fFlags |= kRenderClip;
					}
					else
*/						width = 0;//CalcStringWidth( string );
					fRenderInfo.fX = fRenderInfo.fFarthestX = right - width;
				}
				break;
		}
	}

	// Choose an optimal rendering function
	fRenderInfo.fRenderFunc = nil;
	if( justCalc )
		fRenderInfo.fRenderFunc = IRenderCharNull;
	else if( mip->GetPixelSize() == 32 )
	{
		if( fBPP == 1 )
			fRenderInfo.fRenderFunc = ( fRenderInfo.fFlags & kRenderScaleAA ) ? IRenderChar1To32AA : IRenderChar1To32;
		else if( fBPP == 8 )
		{
			if( fRenderInfo.fFlags & kRenderIntoAlpha )
			{
				if( ( fRenderInfo.fColor & 0xff000000 ) != 0xff000000 )
					fRenderInfo.fRenderFunc = IRenderChar8To32Alpha;
				else
					fRenderInfo.fRenderFunc = IRenderChar8To32FullAlpha;
			}
			else
				fRenderInfo.fRenderFunc = IRenderChar8To32;
		}
	}

	if( fRenderInfo.fRenderFunc == nil )
	{
		hsAssert( false, "Invalid combination of source and destination formats in RenderString()" );
		return;
	}

	// Init our other render values
	if( !justCalc )
	{
		fRenderInfo.fDestStride = mip->GetRowBytes();
		fRenderInfo.fDestBPP = mip->GetPixelSize() >> 3;
		fRenderInfo.fDestPtr = (UInt8 *)mip->GetImage();
		fRenderInfo.fDestPtr += fRenderInfo.fY * fRenderInfo.fDestStride;
		fRenderInfo.fDestPtr += fRenderInfo.fX * fRenderInfo.fDestBPP;
	}
	if( fRenderInfo.fFlags & ( kRenderWrap | kRenderClip ) )
	{
		fRenderInfo.fMaxHeight = (Int16)( fRenderInfo.fClipRect.GetBottom() - fRenderInfo.fY );
		fRenderInfo.fMaxWidth = (Int16)( fRenderInfo.fClipRect.GetRight() - x );
	}
	else if( justCalc )
	{
		// Just calculating, no wrapping, so the max is as far as we can go
		// Note: 32767 isn't quite right, since we'll be adding the left kern in before we
		// calc the first character, so adjust so we make sure we don't underflow
		plCharacter &ch = fCharacters[ (UInt16)(UInt8)string[ 0 ] - fFirstChar ];

		fRenderInfo.fMaxHeight = (Int16)fMaxCharHeight;
		fRenderInfo.fMaxWidth = (Int16)32767 + (Int16)ch.fLeftKern;
	}
	else
	{
		fRenderInfo.fMaxHeight = (Int16)( mip->GetHeight() - fRenderInfo.fY );
		fRenderInfo.fMaxWidth = (Int16)( mip->GetWidth() - x );
	}

	fRenderInfo.fMaxDescent = 0;

	if( fRenderInfo.fFlags & kRenderWrap )
	{
		// Hell, gotta word wrap the text
		// To avoid backtracking, we step forward in the string one word at a time until we hit a break,
		// then render what we have and continue
		Int32 lastWord = 0, i;
		bool isFirstLine = true;
		x = 0;
		Int16 firstMaxAscent = 0;
		UInt32 lineHt, lineDelta;
		if( fRenderInfo.fFlags & kRenderScaleAA )
			lineHt = fMaxCharHeight >> 1;
		else
			lineHt = fMaxCharHeight;
		
		// adjust the line height for spacing
		lineHt += fRenderInfo.fLineSpacing;

		lineDelta = lineHt * fRenderInfo.fDestStride;

		while( *string != 0 && fRenderInfo.fMaxHeight >= fFontDescent )
		{
			UInt8 *destStartPtr = fRenderInfo.fDestPtr;
			UInt32 destStartX = fRenderInfo.fX;
			Int16 destMaxWidth = fRenderInfo.fMaxWidth, thisIndent = 0;

			if( isFirstLine )
			{
				// First line, apply indent if applicable
				fRenderInfo.fX += fRenderInfo.fFirstLineIndent;
				fRenderInfo.fMaxWidth -= fRenderInfo.fFirstLineIndent;
				fRenderInfo.fDestPtr += fRenderInfo.fFirstLineIndent * fRenderInfo.fDestBPP;
				thisIndent = fRenderInfo.fFirstLineIndent;
				isFirstLine = false;
			}

			std::string ellipsisTracker = ""; // keeps track of ellipsis, since there are three word break chars that can't be split
			bool possibleEllipsis = false;
			int preEllipsisLastWord = 0; // where the word break was before we started tracking an ellipsis

			// Iterate through the string, looking for the next line break
			for( lastWord = 0, i = 0; string[ i ] != 0; i++ )
			{
				// If we're a carriage return, we go ahead and break anyway
				if( string[ i ] == L'\n' )
				{
					lastWord = i;
					break;
				}
				
				// handle invalid chars discretely
				plCharacter* charToDraw = NULL;
				if (fCharacters.Count() <= ((UInt16)string[i] - fFirstChar))
					charToDraw = &(fCharacters[(UInt16)L' ' - fFirstChar]);
				else
					charToDraw = &(fCharacters[(UInt16)string[i] - fFirstChar]);

				Int16 leftKern = (Int16)charToDraw->fLeftKern;
				if( fRenderInfo.fFlags & kRenderScaleAA )
					x += leftKern / 2;
				else
					x += leftKern;

				// Update our position and see if we're over
				// Note that our wrapping is slightly off, in that it doesn't take into account
				// the left kerning of characters. Hopefully that won't matter much...
				UInt16 charWidth = (UInt16)(fWidth + (Int16)charToDraw->fRightKern);
				if( fRenderInfo.fFlags & kRenderScaleAA )
					charWidth >>= 1;

				UInt16 nonAdjustedX = (UInt16)(x + fWidth); // just in case the actual bitmap is too big to fit on page and we think the character can (because of right kern)
				x += charWidth;

				if(( x >= fRenderInfo.fMaxWidth ) || (nonAdjustedX >= fRenderInfo.fMaxWidth))
				{
					// we're over, but lastWord may not be correct (especially if we're in the middle of an ellipsis)
					if (possibleEllipsis)
					{
						// ellipsisTracker will not be empty since possibleEllipsis is true (so there will be at least one period)
						if (ellipsisTracker == ".") // only one period so far
						{
							if ((string[i] == '.') && (string[i+1] == '.')) // we have an ellipsis, so reset the lastWord back before we found it
								lastWord = preEllipsisLastWord;
							// otherwise, we don't have an ellipsis, so lastWord is correct (but the grammer might not be ;-)
						}
						else if (ellipsisTracker == "..") // only two periods so far
						{
							if (string[i] == '.') // we have an ellipsis, so reset the lastWord back before we found it
								lastWord = preEllipsisLastWord;
							// otherwise, we don't have an ellipsis, so lastWord is correct (but the grammer might not be ;-)
						}
						// if neither of the above are true, then the full ellipsis was encountered and the lastWord is correct
						ellipsisTracker = "";
						possibleEllipsis = false;
					}
					// Over, so break
					break;
				}

				// Are we a word breaker?
				if( IIsWordBreaker( (char)(string[ i ]) ) )
				{
					if (string[i] == '.') // we might have an ellipsis here
					{
						if (ellipsisTracker == "...") // we already have a full ellipsis, so break between them
						{
							preEllipsisLastWord = i;
							ellipsisTracker = "";
						}
						else if (ellipsisTracker == "") // no ellipsis yet, so save the last word
							preEllipsisLastWord = lastWord;
						ellipsisTracker += '.';
						possibleEllipsis = true;
					}
					else
					{
						ellipsisTracker = ""; // no chance of an ellipsis, so kill it
						possibleEllipsis = false;
					}
					// Yes, and we didn't go over, so store as the last successfully fit word and move on
					lastWord = i;
				}			
			}

			if( string[ i ] == 0 )
				lastWord = i;		// Final catch for end-of-string
			else if( lastWord == 0 && string[ i ] != L'\n' && thisIndent == 0 )
				lastWord = i;		// Catch for a single word that didn't fit (just go up as many chars as we can)
									// (but NOT if we have a first line indent, mind you :)

			// Got to the end of a line (somewhere), so render up to lastWord, then advance from that point
			// to the first non-word-breaker and continue onward
			if( lastWord > 0 )
			{
				bool drawableWordBreak = IIsDrawableWordBreak((char)(string[lastWord]));
				if (drawableWordBreak)
					lastWord++; // make sure we draw it
				if( ( fRenderInfo.fFlags & kRenderJustXMask ) == kRenderJustXRight )
				{
					UInt16 baseX = fRenderInfo.fX, baseMaxW = fRenderInfo.fMaxWidth;
					UInt8 *baseDestPtr = fRenderInfo.fDestPtr;

					fRenderInfo.fX = 0;
					CharRenderFunc oldFunc = fRenderInfo.fRenderFunc;
					fRenderInfo.fRenderFunc = IRenderCharNull;

					IRenderLoop( string, lastWord );

					fRenderInfo.fRenderFunc = oldFunc;
					fRenderInfo.fX = baseX - fRenderInfo.fX;
					fRenderInfo.fMaxWidth = baseMaxW;
					fRenderInfo.fDestPtr = baseDestPtr + ( fRenderInfo.fX - baseX ) * fRenderInfo.fDestBPP;

					IRenderLoop( string, lastWord );

					fRenderInfo.fLastX = fRenderInfo.fX;
					fRenderInfo.fLastY = fRenderInfo.fY;
					fRenderInfo.fX = baseX;
				}
				else if( ( fRenderInfo.fFlags & kRenderJustXMask ) == kRenderJustXCenter )
				{
					UInt16 baseX = fRenderInfo.fX, baseMaxW = fRenderInfo.fMaxWidth;
					UInt8 *baseDestPtr = fRenderInfo.fDestPtr;

					fRenderInfo.fX = 0;
					CharRenderFunc oldFunc = fRenderInfo.fRenderFunc;
					fRenderInfo.fRenderFunc = IRenderCharNull;

					IRenderLoop( string, lastWord );

					fRenderInfo.fRenderFunc = oldFunc;
					fRenderInfo.fX = baseX - ( fRenderInfo.fX >> 1 );
					fRenderInfo.fMaxWidth = baseMaxW;
					fRenderInfo.fDestPtr = baseDestPtr + ( fRenderInfo.fX - baseX ) * fRenderInfo.fDestBPP;

					IRenderLoop( string, lastWord );

					fRenderInfo.fLastX = fRenderInfo.fX;
					fRenderInfo.fLastY = fRenderInfo.fY;
					fRenderInfo.fX = baseX;
				}
				else if( ( fRenderInfo.fFlags & kRenderJustXMask ) == kRenderJustXForceLeft )
				{
					Int16 baseX = fRenderInfo.fX;

					plCharacter &ch = fCharacters[ (UInt16)string[ 0 ] - fFirstChar ];

					fRenderInfo.fX -= (Int16)ch.fLeftKern;
					fRenderInfo.fDestPtr -= (Int16)ch.fLeftKern * fRenderInfo.fDestBPP;

					IRenderLoop( string, lastWord );
				
					fRenderInfo.fLastX = fRenderInfo.fX;
					fRenderInfo.fLastY = fRenderInfo.fY;
					fRenderInfo.fX = baseX;
				}
				else
				{
					IRenderLoop( string, lastWord );
					fRenderInfo.fLastX = fRenderInfo.fX;
					fRenderInfo.fLastY = fRenderInfo.fY;
				}
				if (drawableWordBreak)
					lastWord--; // reset it
			}


			// Store farthest X for later reference
			if( lastWord > 0 && fRenderInfo.fX > fRenderInfo.fFarthestX )
				fRenderInfo.fFarthestX = fRenderInfo.fX;

			if( firstMaxAscent == 0 )
				firstMaxAscent = fRenderInfo.fMaxAscent;

			x = 0;
			fRenderInfo.fX = (Int16)destStartX;
			fRenderInfo.fDestPtr = destStartPtr;
			fRenderInfo.fMaxWidth = destMaxWidth;
			fRenderInfo.fMaxAscent = 0;

			// Look for the next non-word-breaker. Note that if we have any carriage returns hidden in here, 
			// we'll want to be advancing down even further
			// Advance down
			if( string[ i ] != 0 )
			{
				fRenderInfo.fY += (Int16)lineHt;
				fRenderInfo.fMaxHeight -= (Int16)lineHt;
				fRenderInfo.fDestPtr += lineDelta;
				fRenderInfo.fLastX = fRenderInfo.fX;
				fRenderInfo.fLastY = fRenderInfo.fY;
			}
			for( i = lastWord; string[ i ] != 0 && IIsWordBreaker( (char)(string[ i ]) ) && string[ i ] != L'\n'; i++ )
			{
			}
			// Process any trailing carriage returns as a separate loop b/c we don't want to throw away white space
			// after returns
			for( ; string[ i ] == L'\n'; i++ )
			{
				// Don't process if i==lastWord, since we already did that one
				if( i > lastWord && string[ i ] == L'\n' )
				{
					// Advance down
					fRenderInfo.fY += (Int16)lineHt;
					fRenderInfo.fMaxHeight -= (Int16)lineHt;
					fRenderInfo.fDestPtr += lineDelta;
					fRenderInfo.fLastY = fRenderInfo.fY;
				}
			}

			// Keep going from here!
			string += i;
		}

		fRenderInfo.fMaxAscent = firstMaxAscent;	
		// Final y offset from the last line
		fRenderInfo.fY += (Int16)fFontDescent;

		fRenderInfo.fVolatileStringPtr = string; 
	}
	else
	{
		if( fRenderInfo.fFlags & kRenderClip )
		{
			// Advance left past any clipping area
			CharRenderFunc oldFunc = fRenderInfo.fRenderFunc;
			fRenderInfo.fRenderFunc = IRenderCharNull;
			while( fRenderInfo.fX < fRenderInfo.fClipRect.fX && *string != 0 )
			{
				IRenderLoop( string, 1 );
				string++;
			}
			fRenderInfo.fRenderFunc = oldFunc;
		}

		// Adjust for left kern
		if( ( fRenderInfo.fFlags & kRenderJustXMask ) == kRenderJustXForceLeft )
		{
			// See note at top of file
			plCharacter &ch = fCharacters[ (UInt16)string[ 0 ] - fFirstChar ];
			Int32 newX = x - (Int16)ch.fLeftKern;
			if( newX < 0 )
				newX = 0;
			fRenderInfo.fX = fRenderInfo.fFarthestX = (Int16)newX;
		}

		fRenderInfo.fVolatileStringPtr = string;	// Just so we can keep track of when we clip
		IRenderLoop( string, -1 );
		fRenderInfo.fFarthestX = fRenderInfo.fX;
	}
}

void	plFont::IRenderLoop( const wchar_t *string, Int32 maxCount )
{
	// Render the string straight across, one char at a time
	while( *string != 0 && maxCount != 0 )	// Note: if maxCount starts out -1, then it'll just keep 
	{										// decrementing...well ok, not for forever, but pretty close
		UInt16 c = (UInt16)*string;
		if( c < fFirstChar )
			;		// Invalid char
		else
		{
			c -= fFirstChar;
			if( c >= fCharacters.GetCount() )
				;	// Invalid char
			else
			{
				// First pass at supporting left kerning values, but only at the pixel level
				Int16 leftKern = (Int16)fCharacters[ c ].fLeftKern;
				if( leftKern != 0 )
				{
					if( fRenderInfo.fFlags & kRenderScaleAA )
						leftKern /= 2;

					fRenderInfo.fX += leftKern;
					fRenderInfo.fMaxWidth -= leftKern;
					fRenderInfo.fDestPtr += leftKern * fRenderInfo.fDestBPP;
				}

				UInt16 thisWidth = (UInt16)(fWidth + fCharacters[ c ].fRightKern);
				if( fRenderInfo.fFlags & kRenderScaleAA )
					thisWidth >>= 1;

				if( thisWidth >= fRenderInfo.fMaxWidth )
					break;

				(this->*(fRenderInfo.fRenderFunc))( fCharacters[ c ] );

				fRenderInfo.fX += thisWidth;
				fRenderInfo.fMaxWidth -= thisWidth;
				fRenderInfo.fDestPtr += thisWidth * fRenderInfo.fDestBPP;


				Int16 baseline = (Int16)(fCharacters[ c ].fBaseline);
				if( fRenderInfo.fFlags & kRenderScaleAA )
					baseline >>= 1;

				if( baseline > fRenderInfo.fMaxAscent )
					fRenderInfo.fMaxAscent = baseline;

				Int16 thisHt = (Int16)(fCharacters[ c ].fHeight - baseline);
				if( fRenderInfo.fMaxDescent < thisHt )
					fRenderInfo.fMaxDescent = thisHt;
			}
		}

		string++;
		fRenderInfo.fVolatileStringPtr++;
		maxCount--;
	}
}

//// The Rendering Functions //////////////////////////////////////////////////

void	plFont::IRenderChar1To32( const plFont::plCharacter &c )
{
	UInt8	bitMask, *src = fBMapData + c.fBitmapOff;
	UInt32	*destPtr, *destBasePtr = (UInt32 *)( fRenderInfo.fDestPtr - c.fBaseline * fRenderInfo.fDestStride );
	UInt16	x, y;

	
	if( (Int32)c.fHeight - (Int32)c.fBaseline >= fRenderInfo.fMaxHeight || c.fBaseline > fRenderInfo.fY )
		return;

	for( y = 0; y < c.fHeight; y++ )
	{
		destPtr = destBasePtr;
		if( fRenderInfo.fFlags & kRenderItalic )
		{
			// Faux italic
			destPtr += ( c.fHeight - y ) >> 1;
		}

		for( x = 0; x < fRenderInfo.fNumCols; x++ )
		{
			for( bitMask = 0x80; bitMask != 0; bitMask >>= 1 )
			{	
				if( *src & bitMask )
					*destPtr = fRenderInfo.fColor;
				destPtr++;
			}
			src++;
		}
		destBasePtr = (UInt32 *)( (UInt8 *)destBasePtr + fRenderInfo.fDestStride );
	}
}

void	plFont::IRenderChar1To32AA( const plFont::plCharacter &c )
{
	UInt8	bitMask, *src = fBMapData + c.fBitmapOff;
	UInt32	*destPtr, *destBasePtr = (UInt32 *)( fRenderInfo.fDestPtr - ( c.fBaseline >> 1 ) * fRenderInfo.fDestStride );
	UInt16	x, y;

	
	if( ( ( (Int32)c.fHeight - (Int32)c.fBaseline ) >> 1 ) >= fRenderInfo.fMaxHeight || ( c.fBaseline >> 1 ) > fRenderInfo.fY )
		return;

	for( y = 0; y < c.fHeight; y += 2 )
	{
		destPtr = destBasePtr;
		if( fRenderInfo.fFlags & kRenderItalic )
		{
			// Faux italic
			destPtr += ( c.fHeight - y ) >> 2;
		}

		for( x = 0; x < fRenderInfo.fNumCols; x++ )
		{
			for( bitMask = 0x80; bitMask != 0; bitMask >>= 2 )
			{	
				// Grab 4 bits and do 4-to-1 AA
				UInt8 value = ( *src & bitMask ) ? 1 : 0;
				value += ( *src & ( bitMask >> 1 ) ) ? 1 : 0;
				value += ( src[ fRenderInfo.fNumCols ] & bitMask ) ? 1 : 0;
				value += ( src[ fRenderInfo.fNumCols ] & ( bitMask >> 1 ) ) ? 1 : 0;

				switch( value )
				{
					case 1:
					{
						UInt32 src = ( fRenderInfo.fColor >> 2 ) & 0x3f3f3f3f;
						UInt32 dst = ( (*destPtr) >> 2 ) & 0x3f3f3f3f;
						*destPtr = src + dst + dst + dst;					
						break;
					}
					case 2:
					{
						UInt32 src = ( fRenderInfo.fColor >> 1 ) & 0x7f7f7f7f;
						UInt32 dst = ( (*destPtr) >> 1 ) & 0x7f7f7f7f;
						*destPtr = src + dst;
						break;
					}
					case 3:
					{
						UInt32 src = ( fRenderInfo.fColor >> 2 ) & 0x3f3f3f3f;
						UInt32 dst = ( (*destPtr) >> 2 ) & 0x3f3f3f3f;
						*destPtr = src + src + src + dst;
						break;
					}
					case 4:
						*destPtr = fRenderInfo.fColor;
				}

				destPtr++;
			}
			src++;
		}
		destBasePtr = (UInt32 *)( (UInt8 *)destBasePtr + fRenderInfo.fDestStride );
		src += fRenderInfo.fNumCols;
	}
}

void	plFont::IRenderChar8To32( const plFont::plCharacter &c )
{
	UInt8	*src = fBMapData + c.fBitmapOff;
	UInt32	*destPtr, *destBasePtr = (UInt32 *)( fRenderInfo.fDestPtr - c.fBaseline * fRenderInfo.fDestStride );
	UInt16	x, y;
	UInt32	srcAlpha, oneMinusAlpha, r, g, b, dR, dG, dB, destAlpha, thisWidth;
	UInt8	srcR, srcG, srcB;


	// Unfortunately for some fonts, their right kern value actually is
	// farther left than the right edge of the bitmap (think of overlapping
	// script fonts). Ideally, we should store the actual width of each char's
	// bitmap and use that here. However, it really shouldn't make too big of a
	// difference, especially since the dest pixels that we end up overlapping
	// should already be in the cache. If it does, time to upgrade the font
	// format (again)
	thisWidth = fWidth;// + (Int32)c.fRightKern;

	if( (Int32)c.fHeight - (Int32)c.fBaseline >= fRenderInfo.fMaxHeight || thisWidth >= fRenderInfo.fMaxWidth || c.fBaseline > fRenderInfo.fY )
		return;

	srcR = (UInt8)(( fRenderInfo.fColor >> 16 ) & 0x000000ff);
	srcG = (UInt8)(( fRenderInfo.fColor >> 8  ) & 0x000000ff);
	srcB = (UInt8)(( fRenderInfo.fColor       ) & 0x000000ff);

	for( y = 0; y < c.fHeight; y++ )
	{
		destPtr = destBasePtr;
		for( x = 0; x < thisWidth; x++ )
		{
			if( src[ x ] == 255 )
				destPtr[ x ] = fRenderInfo.fColor;
			else if( src[ x ] == 0 )
				;	// Empty
			else
			{
				srcAlpha = ( src[ x ] * ( fRenderInfo.fColor >> 24 ) ) / 255;
				oneMinusAlpha = 255 - srcAlpha;

				destAlpha = destPtr[ x ] & 0xff000000;

				dR = ( destPtr[ x ] >> 16 ) & 0x000000ff;
				dG = ( destPtr[ x ] >> 8  ) & 0x000000ff;
				dB = ( destPtr[ x ]       ) & 0x000000ff;
				r = ( srcR * srcAlpha ) >> 8;
				g = ( srcG * srcAlpha ) >> 8;
				b = ( srcB * srcAlpha ) >> 8;
				dR = ( dR * oneMinusAlpha ) >> 8;
				dG = ( dG * oneMinusAlpha ) >> 8;
				dB = ( dB * oneMinusAlpha ) >> 8;

				destPtr[ x ] = ( ( r + dR ) << 16 ) | ( ( g + dG ) << 8 ) | ( b + dB ) | destAlpha;
			}
		}
		destBasePtr = (UInt32 *)( (UInt8 *)destBasePtr + fRenderInfo.fDestStride );
		src += fWidth;
	}
}

void	plFont::IRenderChar8To32FullAlpha( const plFont::plCharacter &c )
{
	UInt8	*src = fBMapData + c.fBitmapOff;
	UInt32	*destPtr, *destBasePtr = (UInt32 *)( fRenderInfo.fDestPtr - c.fBaseline * fRenderInfo.fDestStride );
	UInt16	x, y;
	UInt32	destColorOnly, thisWidth;


	// Unfortunately for some fonts, their right kern value actually is
	// farther left than the right edge of the bitmap (think of overlapping
	// script fonts). Ideally, we should store the actual width of each char's
	// bitmap and use that here. However, it really shouldn't make too big of a
	// difference, especially since the dest pixels that we end up overlapping
	// should already be in the cache. If it does, time to upgrade the font
	// format (again)
	thisWidth = fWidth;// + (Int32)c.fRightKern;

	if( (Int32)c.fHeight - (Int32)c.fBaseline  >= fRenderInfo.fMaxHeight || thisWidth >= fRenderInfo.fMaxWidth || c.fBaseline > fRenderInfo.fY )
		return;

	destColorOnly = fRenderInfo.fColor & 0x00ffffff;

	for( y = 0; y < c.fHeight; y++ )
	{
		destPtr = destBasePtr;
		for( x = 0; x < thisWidth; x++ )
		{
			if( src[ x ] != 0 )
				destPtr[ x ] = ( src[ x ] << 24 ) | destColorOnly;
		}
		destBasePtr = (UInt32 *)( (UInt8 *)destBasePtr + fRenderInfo.fDestStride );
		src += fWidth;
	}
}

void	plFont::IRenderChar8To32Alpha( const plFont::plCharacter &c )
{
	UInt8	val, *src = fBMapData + c.fBitmapOff;
	UInt32	*destPtr, *destBasePtr = (UInt32 *)( fRenderInfo.fDestPtr - c.fBaseline * fRenderInfo.fDestStride );
	UInt16	x, y;
	UInt32	destColorOnly, alphaMult, fullAlpha, thisWidth;


	// Unfortunately for some fonts, their right kern value actually is
	// farther left than the right edge of the bitmap (think of overlapping
	// script fonts). Ideally, we should store the actual width of each char's
	// bitmap and use that here. However, it really shouldn't make too big of a
	// difference, especially since the dest pixels that we end up overlapping
	// should already be in the cache. If it does, time to upgrade the font
	// format (again)
	thisWidth = fWidth;// + (Int32)c.fRightKern;

	if( (Int32)c.fHeight - (Int32)c.fBaseline  >= fRenderInfo.fMaxHeight || thisWidth >= fRenderInfo.fMaxWidth || c.fBaseline > fRenderInfo.fY )
		return;

	destColorOnly = fRenderInfo.fColor & 0x00ffffff;
	// alphaMult should come out to be a value to satisfy (fontAlpha * alphaMult >> 8) as the right alpha,
	// but then we want it so (fontAlpha * alphaMult) will be in the upper 8 bits
	fullAlpha = fRenderInfo.fColor & 0xff000000;
	alphaMult = fullAlpha / 255;

	for( y = 0; y < c.fHeight; y++ )
	{
		destPtr = destBasePtr;
		for( x = 0; x < thisWidth; x++ )
		{
			val = src[ x ];
			if( val == 0xff )
				destPtr[ x ] = fullAlpha | destColorOnly;
			else if( val != 0 )
			{
				destPtr[ x ] = ( ( alphaMult * val ) & 0xff000000 ) | destColorOnly;
			}
		}
		destBasePtr = (UInt32 *)( (UInt8 *)destBasePtr + fRenderInfo.fDestStride );
		src += fWidth;
	}
}


void	plFont::IRenderCharNull( const plCharacter &c )
{
}

//// CalcString Variations ////////////////////////////////////////////////////

UInt16	plFont::CalcStringWidth( const char *string )
{
	UInt16 w, h, a, lX, lY;
	UInt32 s;
	CalcStringExtents( string, w, h, a, s, lX, lY );
	return w;
}

UInt16	plFont::CalcStringWidth( const wchar_t *string )
{
	UInt16 w, h, a, lX, lY;
	UInt32 s;
	CalcStringExtents( string, w, h, a, s, lX, lY );
	return w;
}

void	plFont::CalcStringExtents( const char *string, UInt16 &width, UInt16 &height, UInt16 &ascent, UInt32 &firstClippedChar, UInt16 &lastX, UInt16 &lastY )
{
	// convert the char string to a wchar_t string
	wchar_t *wideString = hsStringToWString(string);
	CalcStringExtents(wideString,width,height,ascent,firstClippedChar,lastX,lastY);
	delete [] wideString;
}

void	plFont::CalcStringExtents( const wchar_t *string, UInt16 &width, UInt16 &height, UInt16 &ascent, UInt32 &firstClippedChar, UInt16 &lastX, UInt16 &lastY )
{
	IRenderString( nil, 0, 0, string, true );
	width = fRenderInfo.fFarthestX;
	height = (UInt16)(fRenderInfo.fY + fFontDescent);//fRenderInfo.fMaxDescent;
	ascent = fRenderInfo.fMaxAscent;
	lastX = fRenderInfo.fLastX;
	lastY = fRenderInfo.fLastY;

	// firstClippedChar is an index into the given string that points to the start of the part of the string
	// that got clipped (i.e. not rendered).
	firstClippedChar = (UInt32)fRenderInfo.fVolatileStringPtr - (UInt32)string;
	firstClippedChar /= 2; // divide by 2 because a wchar_t is two bytes wide, instead of one (like a char)
}

//// IGetFreeCharData /////////////////////////////////////////////////////////
//	Used for constructing fonts one character at a time; finds a pointer to
//	the first free space in our allocated font bitmap, based on the heights
//	and offsets of all the current characters

UInt8	*plFont::IGetFreeCharData( UInt32 &newOffset )
{
	Int32 i;


	newOffset = 0;
	for( i = fCharacters.GetCount() - 1; i >= 0; i-- )
	{
		UInt32 thisOff = fCharacters[ i ].fBitmapOff + ( ( fCharacters[ i ].fHeight * fWidth * fBPP ) >> 3 );
		if( newOffset < thisOff )
		{
			newOffset = thisOff;
			break;
		}
	}

	if( newOffset >= ( ( fWidth * fHeight * fBPP ) >> 3 ) )
	{
		hsAssert( false, "Invalid position found in IGetFreeCharData()" );
		return nil;
	}

	// Return pointer to the new area
	return fBMapData + newOffset;
}

//// LoadFromP2FFile //////////////////////////////////////////////////////////
//	Handy quick wrapper 

hsBool	plFont::LoadFromP2FFile( const char *path )
{
	hsUNIXStream	stream;
	if( stream.Open( path, "rb" ) )
	{
		ReadRaw( &stream );
		return true;
	}
	return false;
}

//// LoadFromFNT //////////////////////////////////////////////////////////////
//	Load this font from the data found in the given Windows FNT file,
//	using the format specified in the Windows 3 Developers Notes.

hsBool	plFont::LoadFromFNT( const char *path )
{
	hsUNIXStream	stream;		// Ahh, irony
	if( !stream.Open( path, "rb" ) )
		return false;

	return LoadFromFNTStream( &stream );
}

hsBool	plFont::LoadFromFNTStream( hsStream *stream )
{
	IClear();

	try
	{

		// Note: hsUNIXStreams just happen to store in the same endian as Windows...
		struct FNTInfo
		{
			UInt16	version;
			UInt32	size;
			char	copyright[ 60 ];
			UInt16	type;
			UInt16	points;
			UInt16	vertRes;
			UInt16	horzRes;
			UInt16	ascent;
			UInt16	internalLeading;
			UInt16	externalLeading;
			UInt8	italic, underline, strikeout;
			UInt16	weight;
			UInt8	charSet;
			UInt16	pixWidth; // 0 means variable width chars
			UInt16	pixHeight;
			UInt8	pitchFamily;
			UInt16	avgWidth;
			UInt16	maxWidth;
			UInt8	firstChar, lastChar, defaultChar, breakChar;
			UInt16	widthBytes;
			UInt32	device, face;
			UInt32	bitsPointer, bitsOffset;
			UInt8	reserved;
			UInt32	flags;
			UInt16	aSpace, bSpace, cSpace;
			UInt32	colorPointer;
			UInt8	reserved1[ 16 ];

			void	Read( hsStream *s )
			{
				version = s->ReadSwap16();
				size = s->ReadSwap32();

				s->Read( sizeof( copyright ), copyright );
			
				s->ReadSwap( &type );
				s->ReadSwap( &points );
				s->ReadSwap( &vertRes );
				s->ReadSwap( &horzRes );
				s->ReadSwap( &ascent );
				s->ReadSwap( &internalLeading );
				s->ReadSwap( &externalLeading );
				s->ReadSwap( &italic );
				s->ReadSwap( &underline );
				s->ReadSwap( &strikeout );
				s->ReadSwap( &weight );
				s->ReadSwap( &charSet );
				s->ReadSwap( &pixWidth );
				s->ReadSwap( &pixHeight );
				s->ReadSwap( &pitchFamily );
				s->ReadSwap( &avgWidth );
				s->ReadSwap( &maxWidth );
				s->ReadSwap( &firstChar );
				s->ReadSwap( &lastChar );
				s->ReadSwap( &defaultChar );
				s->ReadSwap( &breakChar );
				s->ReadSwap( &widthBytes );
				s->ReadSwap( &device );
				s->ReadSwap( &face );
				s->ReadSwap( &bitsPointer );
				s->ReadSwap( &bitsOffset );
				s->ReadSwap( &reserved );
				if( version == 0x0300 )
				{
					s->ReadSwap( &flags );
					s->ReadSwap( &aSpace );
					s->ReadSwap( &bSpace );
					s->ReadSwap( &cSpace );
					s->ReadSwap( &colorPointer );
					s->Read( sizeof( reserved1 ), reserved1 );
				}
				else
				{
					flags = 0;
					aSpace = bSpace = cSpace = 0;
					colorPointer = 0;
				}
			}
		} fntInfo;

		fntInfo.Read( stream );

		struct charEntry
		{
			UInt16	width;
			UInt32	offset;
			charEntry() { width = 0; offset = 0; }
		} *charEntries;

		int i, count = fntInfo.lastChar - fntInfo.firstChar + 2;
		charEntries = TRACKED_NEW charEntry[ count ];
		for( i = 0; i < count; i++ )
		{
			charEntries[ i ].width = stream->ReadSwap16();
			if( fntInfo.version == 0x0200 )
				charEntries[ i ].offset = stream->ReadSwap16();
			else
				charEntries[ i ].offset = stream->ReadSwap32();
		}

		char faceName[ 256 ], deviceName[ 256 ];
		if( fntInfo.face != 0 )
		{
			stream->SetPosition( fntInfo.face );
			for( i = 0; i < 256; i++ )
			{
				faceName[ i ] = stream->ReadByte();
				if( faceName[ i ] == 0 )
					break;
			}
			strncpy( fFace, faceName, sizeof( fFace ) );
		}
		if( fntInfo.device != 0 )
		{
			stream->SetPosition( fntInfo.device );
			for( i = 0; i < 256; i++ )
			{
				deviceName[ i ] = stream->ReadByte();
				if( deviceName[ i ] == 0 )
					break;
			}
		}
		fSize = (UInt8)(fntInfo.points);

		// Figure out what we need to allocate our bitmap as
		fWidth = 0;
		fHeight = 0;
		for( i = 0; i < count; i++ )
		{
			if( fWidth < charEntries[ i ].width )
				fWidth = charEntries[ i ].width;
			if( charEntries[ i ].offset > 0 )
				fHeight += fntInfo.pixHeight;
		}
		fBPP = 1;

		// Since we're 1 bbp, make sure width is a multiple of 8
		fWidth = ( ( fWidth + 7 ) >> 3 ) << 3;

		UInt32 widthInBytes = ( fWidth * fBPP ) >> 3;

		// Allocate our bitmap now
		UInt32 size = widthInBytes * fHeight;
		fBMapData = TRACKED_NEW UInt8[ size ];
		memset( fBMapData, 0, size );

		fFirstChar = fntInfo.firstChar;
		fMaxCharHeight = 0;

		// Read the bitmap info
		UInt32 destOff = 0;
		for( i = 0; i < count; i++ )
		{
			int numCols = ( charEntries[ i ].width + 7 ) >> 3;
			int height = fntInfo.pixHeight;

			// Write a record for this char
			plCharacter outChar;
			outChar.fBitmapOff = destOff;
			outChar.fHeight = ( charEntries[ i ].offset == 0 ) ? 0 : height;
			outChar.fBaseline = fntInfo.ascent;
			outChar.fLeftKern = 0.f;
			outChar.fRightKern = (hsScalar)(charEntries[ i ].width - fWidth);
			fCharacters.Append( outChar );

			if( outChar.fHeight > fMaxCharHeight )
				fMaxCharHeight = outChar.fHeight;

			if( charEntries[ i ].offset == 0 )
				continue;

			// Seek to this char
			stream->SetPosition( charEntries[ i ].offset );

			// Write the actual bitmap data. Note: FNTs store the bits one column at a time,
			// one col after another, whereas we want it plain row by row
			int col, y;
			UInt8 *basePtr = fBMapData + destOff;
			for( col = 0; col < numCols; col++ )
			{
				UInt8 *yPtr = basePtr;
				for( y = 0; y < height; y++ )
				{
					*yPtr = stream->ReadByte();
					yPtr += widthInBytes;
				}
				basePtr++;
			}

			destOff += widthInBytes * outChar.fHeight;
		}

		delete [] charEntries;

		ICalcFontAscent();
		return true;
	}
	catch( ... )
	{
		// Somehow we crashed converting!
		IClear();
		return false;
	}
}

//// LoadFromBDF //////////////////////////////////////////////////////////////
//	Load this font from the data found in the given Adobe Systems BDF file,
//	using the format specified in the Glyph Bitmap Distribution Format (BDF)
//	Specification Version 2.2 from Adobe Systems.

/*hsBool	plFont::LoadFromBDF( const char *path, plBDFConvertCallback *callback )
{
	hsUNIXStream	stream;		// Ahh, irony
	if( !stream.Open( path, "rb" ) )
		return false;

	return LoadFromBDFStream( &stream, callback );
}
*/
// Some parsing helpers
typedef int	(*fnCharTester)( int );

static int sQuoteTester( int c )
{
	return ( c == '\"' );
}

static int sDashTester( int c )
{
	return isspace( c ) || ( c == '-' );
}

class plLineParser
{
	protected:
		static char	fLine[ 512 ];

		char	*fCursor, fRestore;

		void		IAdvanceToNextToken( fnCharTester tester = isspace )
		{
			// Last cursor
			*fCursor = fRestore;

			// Scan for the start of the next token
			while( *fCursor != 0 && (*tester)( *fCursor ) )
				fCursor++;

			if( *fCursor == 0 )
				return;

			// This is the start of our token
			const char *start = fCursor;

			// Put a stopper here
			fRestore = *fCursor;
			*fCursor = 0;

			// And return!
			return;
		}

		const char	*IGetNextToken( fnCharTester tester = isspace )
		{
			// Last cursor
			*fCursor = fRestore;

			// Scan for the start of the next token
			while( *fCursor != 0 && (*tester)( *fCursor ) )
				fCursor++;

			if( *fCursor == 0 )
				return nil;

			// This is the start of our token; find the end
			const char *start = fCursor;
			while( *fCursor != 0 && !(*tester)( *fCursor ) )
				fCursor++;

			// Put a stopper here
			fRestore = *fCursor;
			*fCursor = 0;

			// And return!
			return start;
		}

	public:

		plLineParser( const char *line )
		{
			strncpy( fLine, line, sizeof( fLine ) );
			fCursor = fLine;
			fRestore = *fCursor;
		}

		~plLineParser() { }

		const char	*GetKeyword( void )
		{
			return IGetNextToken();
		}

		const char	*GetString( void )
		{
			IAdvanceToNextToken();
			return IGetNextToken( sQuoteTester );
		}

		const char	*GetKeywordNoDashes( void )
		{
			return IGetNextToken( sDashTester );
		}

		Int32		GetInt( void )
		{
			return atoi( IGetNextToken() );
		}

		hsScalar	GetFloat( void )
		{
			return (hsScalar)atof( IGetNextToken() );
		}
};

char	plLineParser::fLine[ 512 ];


// Another helper--parser for various sections of the BDF format
class plBDFSectParser
{
	protected:
		plFont					&fFont;
		plBDFConvertCallback	*fCallback;

	public:
		plBDFSectParser( plFont &myFont, plBDFConvertCallback *callback ) : fFont( myFont ), fCallback( callback ) {}

		virtual plBDFSectParser *ParseKeyword( const char *keyword, plLineParser &line )
		{
			return nil;
		}
};


class plBDFLookForEndCharParser : public plBDFSectParser
{
	public:
		plBDFLookForEndCharParser( plFont &myFont, plBDFConvertCallback *callback ) : plBDFSectParser( myFont, callback ) {}

		virtual plBDFSectParser *ParseKeyword( const char *keyword, plLineParser &line );
};

inline UInt8	iHexCharToByte( char c )
{
	switch( c )
	{
		case '0': return 0;
		case '1': return 1;
		case '2': return 2;
		case '3': return 3;
		case '4': return 4;
		case '5': return 5;
		case '6': return 6;
		case '7': return 7;
		case '8': return 8;
		case '9': return 9;
		case 'a': case 'A': return 10;
		case 'b': case 'B': return 11;
		case 'c': case 'C': return 12;
		case 'd': case 'D': return 13;
		case 'e': case 'E': return 14;
		case 'f': case 'F': return 15;
		default: return 0;
	}
}
inline UInt8	iHexStringToByte( const char *str )
{
	return ( iHexCharToByte( str[ 0 ] ) << 4 ) | iHexCharToByte( str[ 1 ] );
}

class plBDFCharsParser : public plBDFSectParser
{
	protected:

		// Info about the current character we're translating
		UInt16				fWhichChar, fRowsLeft;
		plFont::plCharacter	*fCharacter;
		UInt8				*fBitmap;
		hsBool				fDoingData;
		UInt32				fBytesWide, fBMapStride;

		inline void	IReset( void )
		{
			fBitmap = nil;
			fCharacter = nil;
			fWhichChar = 0;
			fDoingData = false;
		}

	public:
		static UInt32		fResolution;

		plBDFCharsParser( plFont &myFont, plBDFConvertCallback *callback ) : plBDFSectParser( myFont, callback ), fDoingData( false ) {}

		virtual plBDFSectParser *ParseKeyword( const char *keyword, plLineParser &line )
		{
			if( strcmp( keyword, "ENDFONT" ) == 0 )
			{
				// All done!
				return nil;
			}
			else if( strcmp( keyword, "ENDCHAR" ) == 0 )
			{
				// End of the character, reset
				IReset();
				if( fCallback != nil )
					fCallback->CharDone();
			}
			else if( fDoingData )
			{
				// If we're doing data, all lines are hex values until we hit "ENDCHAR"
				if( fRowsLeft == 0 )
					throw;

				int hDigit;
				for( hDigit = 0; *keyword != 0 && hDigit < fBytesWide; hDigit++, keyword += 2 )
				{
					*fBitmap = iHexStringToByte( keyword );
					fBitmap++;
				}

				fBitmap += fBMapStride - hDigit;
				fRowsLeft--;
			}
			else if( strcmp( keyword, "STARTCHAR" ) == 0 )
			{
				// Start of a new character--ignore the name, we'll just use the encoding
				IReset();
			}
			else if( strcmp( keyword, "ENCODING" ) == 0 )
			{
				int ch = line.GetInt();
				if( ch == -1 )
				{
					// Nonstandard encoding, skip the character entirely
					return TRACKED_NEW plBDFLookForEndCharParser( fFont, fCallback );
				}
				else
				{
					fWhichChar = (UInt16)(UInt8)ch;

					// Set up our pointer. Note that since BDFs don't tell us the starting character,
					// we just make it 0
					if( fFont.fCharacters.GetCount() < fWhichChar + 1 )
						fFont.fCharacters.SetCount( fWhichChar + 1 );

					fCharacter = &fFont.fCharacters[ fWhichChar ];
				}
			}
			// Horizontal mode offsets (writing mode 0)
			else if( strcmp( keyword, "SWIDTH" ) == 0 )
			{
				// In device units, unused
//				float fWidth = (float)line.GetInt() * ( (float)fFont.GetSize() / 1000.f ) * ( fResolution / 72.f );
//				fCharacter->fRightKern = fWidth - (float)fFont.fWidth;
//				fCharacter->fLeftKern = 0.f;
			}
			else if( strcmp( keyword, "DWIDTH" ) == 0 )
			{
				// In pixels, basically the offset to the next char. Note that we only
				// support the X direction (altho admittedly the idea of a font whose characters cause
				// the next chars to go up or down is way nifty)
				fCharacter->fRightKern = (float)line.GetInt() - (float)fFont.fWidth;
				fCharacter->fLeftKern = 0.f;
			}
			// Vertical mode offsets (unsupported)
			else if( strcmp( keyword, "SWIDTH1" ) == 0 )
			{

			}
			else if( strcmp( keyword, "DWIDTH1" ) == 0 )
			{

			}
			// Bitmap bounding box
			else if( strcmp( keyword, "BBX" ) == 0 )
			{
				int pixW = line.GetInt();
				int pixH = line.GetInt();
				int xOff = line.GetInt();
				int yOff = line.GetInt();

				// Got enough info now to allocate us a bitmap for this char
				fBitmap = fFont.IGetFreeCharData( fCharacter->fBitmapOff );
				if( fBitmap == nil )
					throw false;

				if( fCharacter->fBitmapOff > ( ( fFont.fWidth * ( fFont.fHeight - pixH ) * fFont.fBPP ) >> 3 ) )
				{
					hsAssert( false, "Invalid position found in IGetFreeCharData()" );
					return nil;
				}

				// Set these now, since setting them before would've changed the IGetFreeCharData results
				fCharacter->fLeftKern = (hsScalar)xOff;
				fCharacter->fBaseline = yOff + pixH;
				fCharacter->fHeight = fRowsLeft = pixH;

				fBytesWide = ( pixW + 7 ) >> 3;
				fBMapStride = fFont.fWidth >> 3;
			}
			// Bitmap data
			else if( strcmp( keyword, "BITMAP" ) == 0 )
			{
				fDoingData = true;
			}
			// This keyword is outputted from the "getbdf" utility on linux, for some reason...
			else if( strcmp( keyword, "ATTRIBUTES" ) == 0 )
			{

			}
			else
				throw false;		// Invalid keyword

			return this;
		}
};

UInt32 plBDFCharsParser::fResolution = 72;


plBDFSectParser *plBDFLookForEndCharParser::ParseKeyword( const char *keyword, plLineParser &line )
{
	if( strcmp( keyword, "ENDCHAR" ) == 0 )
	{
		// Horray!
		return TRACKED_NEW plBDFCharsParser( fFont, fCallback );
	}
	// Just gobble lines until we find the endchar section
	return this;
}

class plBDFLookForCharParser : public plBDFSectParser
{
	public:
		plBDFLookForCharParser( plFont &myFont, plBDFConvertCallback *callback ) : plBDFSectParser( myFont, callback ) {}

		virtual plBDFSectParser *ParseKeyword( const char *keyword, plLineParser &line )
		{
			if( strcmp( keyword, "CHARS" ) == 0 )
			{
				// Horray!
				return TRACKED_NEW plBDFCharsParser( fFont, fCallback );
			}
			// Just gobble lines until we find the chars section
			return this;
		}
};

class plBDFPropertiesParser : public plBDFSectParser
{
	public:
		plBDFPropertiesParser( plFont &myFont, plBDFConvertCallback *callback ) : plBDFSectParser( myFont, callback ) {}

		virtual plBDFSectParser *ParseKeyword( const char *keyword, plLineParser &line )
		{
			// Note: the properties section is entirely optional and arbitrary, but we
			// parse it in case we can get more accurate info about the font name and props
			if( strcmp( keyword, "FACE_NAME" ) == 0 )
			{
				fFont.SetFace( line.GetString() );
			}
			else if( strcmp( keyword, "WEIGHT_NAME" ) == 0 )
			{
				if( stricmp( line.GetString(), "Bold" ) == 0 )
					fFont.SetFlag( plFont::kFlagBold, true );
			}
			else if( strcmp( keyword, "ENDPROPERTIES" ) == 0 )
			{
				// Switch to waiting for the chars section
				return TRACKED_NEW plBDFLookForCharParser( fFont, fCallback );
			}
			// All tokens are technically valid, even if we don't recognize them
			return this;
		}
};

class plBDFHeaderParser : public plBDFSectParser
{
	public:
		plBDFHeaderParser( plFont &myFont, plBDFConvertCallback *callback ) : plBDFSectParser( myFont, callback ) {}

		virtual plBDFSectParser *ParseKeyword( const char *keyword, plLineParser &line )
		{
			if( strcmp( keyword, "STARTFONT" ) == 0 )
			{
				// Start of the font; check version #
				float version = line.GetFloat();
				if( version < 2.1f )
					throw false;

				// Initial font values
				fFont.fFirstChar = 0;
				fFont.fMaxCharHeight = 0;
			}
			else if( strcmp( keyword, "FONT" ) == 0 )
			{
				// Postscript-style font name: figure out our face name and hopefully attributes too
				// Format is usually of the form: vendor-face-weight-otherStuff
				const char *vendor = line.GetKeywordNoDashes();
				const char *face = line.GetKeywordNoDashes();

				fFont.SetFace( face != nil ? face : vendor );

				const char *weight = line.GetKeywordNoDashes();

				if( weight != nil && stricmp( weight, "Bold" ) == 0 )
					fFont.SetFlag( plFont::kFlagBold, true );
			}
			else if( strcmp( keyword, "COMMENT" ) == 0 )
			{
				// Comment, ignore
			}
			else if( strcmp( keyword, "CONTENTVERSION" ) == 0 )
			{
				// Content version, app specific, not used by us
			}
			else if( strcmp( keyword, "SIZE" ) == 0 )
			{
				// Point size and target resolution of this font
				fFont.SetSize( (UInt8)(line.GetInt()) );
				plBDFCharsParser::fResolution = line.GetInt();
			}
			else if( strcmp( keyword, "FONTBOUNDINGBOX" ) == 0 )
			{
				// Max dimensions and base offsets
				// Note that we should've already grabbed the width from our
				// prescan earlier, to guard against malformed BDFs
				int thisWidth = line.GetInt();
				if( fFont.fWidth < thisWidth )
					fFont.fWidth = thisWidth;

				// Since we're 1 bbp, make sure width is a multiple of 8
				fFont.fWidth = ( ( fFont.fWidth + 7 ) >> 3 ) << 3;

				// Allocate our data now
				fFont.fBMapData = TRACKED_NEW UInt8[ ( fFont.fWidth * fFont.fHeight * fFont.fBPP ) >> 3 ];
				memset( fFont.fBMapData, 0, ( fFont.fWidth * fFont.fHeight * fFont.fBPP ) >> 3 );

				fFont.fMaxCharHeight = line.GetInt();
			}
			else if( strcmp( keyword, "METRICSSET" ) == 0 )
			{
				// Metrics set specification; unsupported
			}
			else if( strcmp( keyword, "STARTPROPERTIES" ) == 0 )
			{
				// Start of the properties section, switch to that
				return TRACKED_NEW plBDFPropertiesParser( fFont, fCallback );
			}
			else if( strcmp( keyword, "CHARS" ) == 0 )
			{
				// Allocate our bitmap if we haven't already

				// Since we're 1 bbp, make sure width is a multiple of 8
				if( fFont.fBMapData == nil )
				{
					fFont.fWidth = ( ( fFont.fWidth + 7 ) >> 3 ) << 3;

					// Allocate our data now
					fFont.fBMapData = TRACKED_NEW UInt8[ ( fFont.fWidth * fFont.fHeight * fFont.fBPP ) >> 3 ];
					memset( fFont.fBMapData, 0, ( fFont.fWidth * fFont.fHeight * fFont.fBPP ) >> 3 );
				}

				// Start of the char section
				return TRACKED_NEW plBDFCharsParser( fFont, fCallback );
			}
			else
			{
				// Unrecognized token, abort
				throw false;
			}

			// Default, keep going with our parser
			return this;
		}
};

class plBDFCheckDimsParser : public plBDFSectParser
{
		hsBool	fSkipNext;

	public:

		UInt32	fMaxWidth, fMaxHeight, fNumChars, fTotalHeight;
		UInt16	fMaxChar;

		plBDFCheckDimsParser( plFont &myFont ) : plBDFSectParser( myFont, nil ) { fMaxWidth = fMaxHeight = fNumChars = fTotalHeight = 0; fSkipNext = false; fMaxChar = 0; }

		virtual plBDFSectParser *ParseKeyword( const char *keyword, plLineParser &line )
		{
			if( strcmp( keyword, "ENDFONT" ) == 0 )
			{
				// All done!
				return nil;
			}
			// Encoding
			else if( strcmp( keyword, "ENCODING" ) == 0 )
			{
				int ch = line.GetInt();
				if( ch == -1 )
					// We skip these
					fSkipNext = true;
				else
				{
					fSkipNext = false;
					if( fMaxChar < ch )
						fMaxChar = ch;
				}
			}
			// Bitmap bounding box
			else if( strcmp( keyword, "BBX" ) == 0 )
			{
				if( fSkipNext )
					return this;

				int pixW = line.GetInt();
				int pixH = line.GetInt();
				int xOff = line.GetInt();
				int yOff = line.GetInt();

				if( fMaxWidth < pixW )
					fMaxWidth = pixW;
				if( fMaxHeight < pixH )
					fMaxHeight = pixH;

				fNumChars++;

				fTotalHeight += pixH;
			}
			return this;
		}
};

hsBool	plFont::LoadFromBDFStream( hsStream *stream, plBDFConvertCallback *callback )
{
	return false;
}

hsBool	plFont::LoadFromBDF( const char *path, plBDFConvertCallback *callback )
{
	FILE *fp = fopen( path, "rt" );
	if( fp == nil )
		return false;
	try
	{
		IClear();

		char	line[ 512 ];

		// Run through the entire file first with a plBDFCheckDimsParser. This is because
		// some BDFs are naughty and don't report the correct fontboundingbox (i.e. too small)
		// (See the following loop below for details on the workings of this loop)
		plBDFCheckDimsParser	checkDims( *this );
		while( fgets( line, sizeof( line ), fp ) )
		{
			plLineParser	parser( line );
			const char *keyword = parser.GetKeyword();
			if( keyword != nil )
			{
				if( checkDims.ParseKeyword( keyword, parser ) == nil )
					break;
			}
		}

		// Set up initial values
		fWidth = checkDims.fMaxWidth;
		fHeight = checkDims.fTotalHeight;
		fBPP = 1;

		// Pre-expand our char list
		fCharacters.ExpandAndZero( checkDims.fMaxChar + 1 );
		fCharacters.SetCount( 0 );

		if( callback != nil )
			callback->NumChars( (UInt16)(checkDims.fNumChars) );

		// Rewind and continue normally
		fseek( fp, 0, SEEK_SET );

		// Start with the header parser
		plBDFSectParser *currParser = TRACKED_NEW plBDFHeaderParser( *this, callback );

		// Read from the stream one line at a time (don't want comments, and char #1 should be invalid for a BDF)
		while( fgets( line, sizeof( line ), fp ) && currParser != nil )
		{
			// Parse this one
			plLineParser	parser( line );

			// Get the keyword for this line
			const char *keyword = parser.GetKeyword();
			if( keyword != nil )
			{
				// Pass on to the current parser
				plBDFSectParser *newParser = currParser->ParseKeyword( keyword, parser );
				if( newParser != currParser )
				{
					delete currParser;
					currParser = newParser;
				}
			}
		}
	}
	catch( ... )
	{
		IClear();
		return false;
	}

	fclose( fp );

	ICalcFontAscent();
	return true;
}

hsBool	plFont::ReadRaw( hsStream *s )
{
	s->Read( sizeof( fFace ), fFace );
	fSize = s->ReadByte();
	s->ReadSwap( &fFlags );

	s->ReadSwap( &fWidth );
	s->ReadSwap( &fHeight );
	s->ReadSwap( &fMaxCharHeight );

	fBPP = s->ReadByte();

	UInt32 size = ( fWidth * fHeight * fBPP ) >> 3;
	if( size > 0 )
	{
		fBMapData = TRACKED_NEW UInt8[ size ];
		s->Read( size, fBMapData );
	}
	else
		fBMapData = nil;

	s->ReadSwap( &fFirstChar );

	UInt32 i;
	fCharacters.SetCountAndZero( s->ReadSwap32() );
	for( i = 0; i < fCharacters.GetCount(); i++ )
		fCharacters[ i ].Read( s );

	ICalcFontAscent();

	return true;
}

hsBool	plFont::WriteRaw( hsStream *s )
{
	s->Write( sizeof( fFace ), fFace );
	s->WriteByte( fSize );
	s->WriteSwap( fFlags );

	s->WriteSwap( fWidth );
	s->WriteSwap( fHeight );
	s->WriteSwap( fMaxCharHeight );

	s->WriteByte( fBPP );

	UInt32 size = ( fWidth * fHeight * fBPP ) >> 3;
	if( size > 0 )
		s->Write( size, fBMapData );

	s->WriteSwap( fFirstChar );

	UInt32 i;
	s->WriteSwap32( fCharacters.GetCount() );
	for( i = 0; i < fCharacters.GetCount(); i++ )
		fCharacters[ i ].Write( s );

	return true;
}