/*==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 3 ds 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 , 3 ds 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 = & plFont : : IRenderCharNull ;
else if ( mip - > GetPixelSize ( ) = = 32 )
{
if ( fBPP = = 1 )
fRenderInfo . fRenderFunc = ( fRenderInfo . fFlags & kRenderScaleAA ) ? & plFont : : IRenderChar1To32AA : & plFont : : IRenderChar1To32 ;
else if ( fBPP = = 8 )
{
if ( fRenderInfo . fFlags & kRenderIntoAlpha )
{
if ( ( fRenderInfo . fColor & 0xff000000 ) ! = 0xff000000 )
fRenderInfo . fRenderFunc = & plFont : : IRenderChar8To32Alpha ;
else
fRenderInfo . fRenderFunc = & plFont : : IRenderChar8To32FullAlpha ;
}
else
fRenderInfo . fRenderFunc = & plFont : : 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 = & plFont : : 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 = & plFont : : 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 = & plFont : : 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 ;
}