You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

1966 lines
56 KiB

/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
///////////////////////////////////////////////////////////////////////////////
// //
// 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;
}