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.
2177 lines
62 KiB
2177 lines
62 KiB
4 years ago
|
/*==LICENSE==*
|
||
|
|
||
|
CyanWorlds.com Engine - MMOG client, server and tools
|
||
|
Copyright (C) 2011 Cyan Worlds, Inc.
|
||
|
|
||
|
This program is free software: you can redistribute it and/or modify
|
||
|
it under the terms of the GNU General Public License as published by
|
||
|
the Free Software Foundation, either version 3 of the License, or
|
||
|
(at your option) any later version.
|
||
|
|
||
|
This program is distributed in the hope that it will be useful,
|
||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
GNU General Public License for more details.
|
||
|
|
||
|
You should have received a copy of the GNU General Public License
|
||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
|
||
|
Additional permissions under GNU GPL version 3 section 7
|
||
|
|
||
|
If you modify this Program, or any covered work, by linking or
|
||
|
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
|
||
|
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
|
||
|
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
|
||
|
(or a modified version of those libraries),
|
||
|
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
|
||
|
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
|
||
|
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
|
||
|
licensors of this Program grant you additional
|
||
|
permission to convey the resulting work. Corresponding Source for a
|
||
|
non-source form of such a combination shall include the source code for
|
||
|
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
|
||
|
work.
|
||
|
|
||
|
You can contact Cyan Worlds, Inc. by email legal@cyan.com
|
||
|
or by snail mail at:
|
||
|
Cyan Worlds, Inc.
|
||
|
14617 N Newport Hwy
|
||
|
Mead, WA 99021
|
||
|
|
||
|
*==LICENSE==*/
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
// //
|
||
|
// plFont Class Header //
|
||
|
// Seems like we've come full circle again. This is our generic Plasma //
|
||
|
// bitmap font class/format. Quick list of things it supports, or needs to: //
|
||
|
// - Antialiasing, either in the font def or at rendertime //
|
||
|
// - Doublebyte character sets //
|
||
|
// - Platform independence, of course //
|
||
|
// - Render to reasonably arbitrary mipmap //
|
||
|
// - Character-level kerning, both before and after, as well as //
|
||
|
// negative kerning (for ligatures) //
|
||
|
// //
|
||
|
// Cyan, Inc. //
|
||
|
// //
|
||
|
//// Version History //////////////////////////////////////////////////////////
|
||
|
// //
|
||
|
// 3.4.2003 mcn - Created. //
|
||
|
// 4.3.2003 mcn - Updated. Casting a char to a UInt16 sign-extends it if //
|
||
|
// the char is > 128, but casting it to an UInt8 first works.//
|
||
|
// Ugly as sin, but hey, so are you. //
|
||
|
// //
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
#include "hsTypes.h"
|
||
|
#include "hsStlUtils.h"
|
||
|
#include "plFont.h"
|
||
|
|
||
|
#include "plMipmap.h"
|
||
|
#include "hsResMgr.h"
|
||
|
|
||
|
|
||
|
//// plCharacter Stuff ////////////////////////////////////////////////////////
|
||
|
|
||
|
plFont::plCharacter::plCharacter()
|
||
|
{
|
||
|
fBitmapOff = 0;
|
||
|
fHeight = 0;
|
||
|
fBaseline = 0;
|
||
|
fLeftKern = fRightKern = 0.f;
|
||
|
}
|
||
|
|
||
|
void plFont::plCharacter::Read( hsStream *s )
|
||
|
{
|
||
|
// Rocket science here...
|
||
|
s->ReadSwap( &fBitmapOff );
|
||
|
s->ReadSwap( &fHeight );
|
||
|
s->ReadSwap( &fBaseline );
|
||
|
s->ReadSwap( &fLeftKern );
|
||
|
s->ReadSwap( &fRightKern );
|
||
|
}
|
||
|
|
||
|
void plFont::plCharacter::Write( hsStream *s )
|
||
|
{
|
||
|
s->WriteSwap( fBitmapOff );
|
||
|
s->WriteSwap( fHeight );
|
||
|
s->WriteSwap( fBaseline );
|
||
|
s->WriteSwap( fLeftKern );
|
||
|
s->WriteSwap( fRightKern );
|
||
|
}
|
||
|
|
||
|
//// Constructor/Read/Write/Destructor/etc ////////////////////////////////////
|
||
|
|
||
|
plFont::plFont()
|
||
|
{
|
||
|
IClear( true );
|
||
|
}
|
||
|
|
||
|
plFont::~plFont()
|
||
|
{
|
||
|
IClear();
|
||
|
}
|
||
|
|
||
|
void plFont::IClear( bool onConstruct )
|
||
|
{
|
||
|
if( !onConstruct )
|
||
|
delete [] fBMapData;
|
||
|
|
||
|
memset( fFace, 0, sizeof( fFace ) );
|
||
|
fSize = 0;
|
||
|
fFlags = 0;
|
||
|
|
||
|
fWidth = fHeight = 0;
|
||
|
fBPP = 0;
|
||
|
fBMapData = nil;
|
||
|
fFirstChar = 0;
|
||
|
fMaxCharHeight = 0;
|
||
|
fCharacters.Reset();
|
||
|
|
||
|
fRenderInfo.fFlags = 0;
|
||
|
fRenderInfo.fX = fRenderInfo.fY = fRenderInfo.fNumCols = 0;
|
||
|
fRenderInfo.fMaxWidth = fRenderInfo.fMaxHeight = 0;
|
||
|
fRenderInfo.fDestPtr = nil;
|
||
|
fRenderInfo.fDestStride = 0;
|
||
|
fRenderInfo.fColor = 0;
|
||
|
fRenderInfo.fMipmap = nil;
|
||
|
fRenderInfo.fRenderFunc = nil;
|
||
|
fRenderInfo.fVolatileStringPtr = nil;
|
||
|
fRenderInfo.fFirstLineIndent = 0;
|
||
|
fRenderInfo.fLineSpacing = 0;
|
||
|
}
|
||
|
|
||
|
void plFont::SetFace( const char *face )
|
||
|
{
|
||
|
strncpy( fFace, face, sizeof( fFace ) );
|
||
|
}
|
||
|
|
||
|
void plFont::SetSize( UInt8 size )
|
||
|
{
|
||
|
fSize = size;
|
||
|
}
|
||
|
|
||
|
void plFont::Read( hsStream *s, hsResMgr *mgr )
|
||
|
{
|
||
|
hsKeyedObject::Read( s, mgr );
|
||
|
ReadRaw( s );
|
||
|
}
|
||
|
|
||
|
void plFont::Write( hsStream *s, hsResMgr *mgr )
|
||
|
{
|
||
|
hsKeyedObject::Write( s, mgr );
|
||
|
WriteRaw( s );
|
||
|
}
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
//// Rendering ////////////////////////////////////////////////////////////////
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
void plFont::SetRenderColor( UInt32 color )
|
||
|
{
|
||
|
fRenderInfo.fColor = color;
|
||
|
}
|
||
|
|
||
|
void plFont::SetRenderClipRect( Int16 x, Int16 y, Int16 width, Int16 height )
|
||
|
{
|
||
|
fRenderInfo.fClipRect.Set( x, y, width, height );
|
||
|
}
|
||
|
|
||
|
void plFont::SetRenderClipping( Int16 x, Int16 y, Int16 width, Int16 height )
|
||
|
{
|
||
|
SetRenderFlag( kRenderWrap, false );
|
||
|
SetRenderFlag( kRenderClip, true );
|
||
|
SetRenderClipRect( x, y, width, height );
|
||
|
}
|
||
|
|
||
|
void plFont::SetRenderWrapping( Int16 x, Int16 y, Int16 width, Int16 height )
|
||
|
{
|
||
|
SetRenderFlag( kRenderWrap, true );
|
||
|
SetRenderFlag( kRenderClip, false );
|
||
|
SetRenderClipRect( x, y, width, height );
|
||
|
}
|
||
|
|
||
|
void plFont::ICalcFontAscent( void )
|
||
|
{
|
||
|
UInt32 i;
|
||
|
|
||
|
|
||
|
// Hack for now, only calc the ascent for characters in the 127 character ASCII range
|
||
|
for( i = 0, fFontAscent = 0, fFontDescent = 0, fMaxCharHeight = 0; i < fCharacters.GetCount(); i++ )
|
||
|
{
|
||
|
if( i + fFirstChar < 128 && fFontAscent < fCharacters[ i ].fBaseline )
|
||
|
fFontAscent = fCharacters[ i ].fBaseline;
|
||
|
|
||
|
Int32 descent = fCharacters[ i ].fHeight - fCharacters[ i ].fBaseline;
|
||
|
if( fFontDescent < descent )
|
||
|
fFontDescent = descent;
|
||
|
|
||
|
if( fMaxCharHeight < fCharacters[ i ].fHeight )
|
||
|
fMaxCharHeight = fCharacters[ i ].fHeight;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//// IIsWordBreaker //////////////////////////////////////////////////////////
|
||
|
// Returns whether the given character is one that can break a line
|
||
|
|
||
|
static inline bool IIsWordBreaker( const char c )
|
||
|
{
|
||
|
return ( strchr( " \t,.;\n", c ) != nil ) ? true : false;
|
||
|
}
|
||
|
|
||
|
//// IIsDrawableWordBreak /////////////////////////////////////////////////////
|
||
|
// Returns whether the given character is a line breaker that has to be drawn
|
||
|
// (non whitespace)
|
||
|
|
||
|
static inline bool IIsDrawableWordBreak( const char c )
|
||
|
{
|
||
|
return ( strchr( ",.;", c ) != nil ) ? true : false;
|
||
|
}
|
||
|
|
||
|
//// RenderString /////////////////////////////////////////////////////////////
|
||
|
// The base render function. Additional options are specified externally,
|
||
|
// so that their effects can be cached for optimization
|
||
|
|
||
|
void plFont::RenderString( plMipmap *mip, UInt16 x, UInt16 y, const char *string, UInt16 *lastX, UInt16 *lastY )
|
||
|
{
|
||
|
// convert the char string to a wchar_t string
|
||
|
wchar_t *wideString = hsStringToWString(string);
|
||
|
RenderString(mip,x,y,wideString,lastX,lastY);
|
||
|
delete [] wideString;
|
||
|
}
|
||
|
|
||
|
|
||
|
void plFont::RenderString( plMipmap *mip, UInt16 x, UInt16 y, const wchar_t *string, UInt16 *lastX, UInt16 *lastY )
|
||
|
{
|
||
|
if( mip->IsCompressed() )
|
||
|
{
|
||
|
hsAssert( false, "Unable to render string to compressed mipmap" );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
IRenderString( mip, x, y, string, false );
|
||
|
if( lastX != nil )
|
||
|
*lastX = fRenderInfo.fLastX;
|
||
|
if( lastY != nil )
|
||
|
*lastY = fRenderInfo.fLastY;
|
||
|
}
|
||
|
|
||
|
void plFont::IRenderString( plMipmap *mip, UInt16 x, UInt16 y, const wchar_t *string, hsBool justCalc )
|
||
|
{
|
||
|
fRenderInfo.fMipmap = mip;
|
||
|
fRenderInfo.fX = x;
|
||
|
fRenderInfo.fY = y;
|
||
|
fRenderInfo.fNumCols = (Int16)(( fBPP <= 8 ) ? ( ( fWidth * fBPP ) >> 3 ) : 0);
|
||
|
fRenderInfo.fFloatWidth = (hsScalar)fWidth;
|
||
|
fRenderInfo.fFarthestX = x;
|
||
|
fRenderInfo.fMaxAscent = 0;
|
||
|
fRenderInfo.fVolatileStringPtr = string;
|
||
|
|
||
|
switch( fRenderInfo.fFlags & kRenderJustYMask )
|
||
|
{
|
||
|
case kRenderJustYTop:
|
||
|
fRenderInfo.fY += (Int16)fFontAscent;
|
||
|
break;
|
||
|
case kRenderJustYBottom:
|
||
|
if( fRenderInfo.fFlags & ( kRenderClip | kRenderWrap ) )
|
||
|
fRenderInfo.fY = (Int16)(fRenderInfo.fClipRect.GetBottom() - 1 - fMaxCharHeight + fFontAscent);
|
||
|
else
|
||
|
fRenderInfo.fY = (Int16)(mip->GetHeight() - 1 - fMaxCharHeight + fFontAscent);
|
||
|
break;
|
||
|
case kRenderJustYCenter:
|
||
|
fRenderInfo.fY = (Int16)(( fRenderInfo.fFlags & ( kRenderClip | kRenderWrap ) ) ? fRenderInfo.fClipRect.GetBottom() - 1 : mip->GetHeight() - 1);
|
||
|
fRenderInfo.fY = (Int16)(( fRenderInfo.fY - fMaxCharHeight ) >> 1);
|
||
|
fRenderInfo.fY += (Int16)fFontAscent;
|
||
|
break;
|
||
|
default: // Just the baseline
|
||
|
;
|
||
|
}
|
||
|
|
||
|
if( justCalc )
|
||
|
{
|
||
|
plCharacter &ch = fCharacters[ (UInt16)string[ 0 ] - fFirstChar ];
|
||
|
fRenderInfo.fX = fRenderInfo.fFarthestX = x - (Int16)ch.fLeftKern;
|
||
|
if( fRenderInfo.fX < 0 )
|
||
|
fRenderInfo.fX = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
switch( fRenderInfo.fFlags & kRenderJustXMask )
|
||
|
{
|
||
|
case kRenderJustXLeft:
|
||
|
// Default
|
||
|
break;
|
||
|
case kRenderJustXForceLeft:
|
||
|
{
|
||
|
// plCharacter &ch = fCharacters[ (UInt16)(UInt8)string[ 0 ] - fFirstChar ];
|
||
|
// Int32 newX = x - (Int16)ch.fLeftKern;
|
||
|
// if( newX < 0 )
|
||
|
// newX = 0;
|
||
|
// fRenderInfo.fX = fRenderInfo.fFarthestX = newX;
|
||
|
}
|
||
|
break;
|
||
|
case kRenderJustXCenter:
|
||
|
{
|
||
|
UInt16 right = (UInt16)(( fRenderInfo.fFlags & ( kRenderClip | kRenderWrap ) ) ? fRenderInfo.fClipRect.GetRight() : mip->GetWidth());
|
||
|
// UInt16 width = CalcStringWidth( string );
|
||
|
fRenderInfo.fX = fRenderInfo.fFarthestX = ( ( x + right ) >> 1 );// - ( width >> 1 );
|
||
|
}
|
||
|
break;
|
||
|
case kRenderJustXRight:
|
||
|
{
|
||
|
UInt16 width = 0, right = (UInt16)(( fRenderInfo.fFlags & ( kRenderClip | kRenderWrap ) ) ? fRenderInfo.fClipRect.GetRight() : mip->GetWidth());
|
||
|
/* if( fRenderInfo.fFlags & kRenderClip )
|
||
|
{
|
||
|
fRenderInfo.fFlags &= ~kRenderClip;
|
||
|
width = CalcStringWidth( string );
|
||
|
fRenderInfo.fFlags |= kRenderClip;
|
||
|
}
|
||
|
else
|
||
|
*/ width = 0;//CalcStringWidth( string );
|
||
|
fRenderInfo.fX = fRenderInfo.fFarthestX = right - width;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Choose an optimal rendering function
|
||
|
fRenderInfo.fRenderFunc = nil;
|
||
|
if( justCalc )
|
||
|
fRenderInfo.fRenderFunc = &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.fFlags & kRenderAlphaPremultiplied )
|
||
|
{
|
||
|
if (fRenderInfo.fFlags & kRenderShadow)
|
||
|
fRenderInfo.fRenderFunc = &plFont::IRenderChar8To32AlphaPremShadow;
|
||
|
else
|
||
|
fRenderInfo.fRenderFunc = &plFont::IRenderChar8To32AlphaPremultiplied;
|
||
|
}
|
||
|
else 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;
|
||
|
Int16 prevX;
|
||
|
do
|
||
|
{
|
||
|
prevX = fRenderInfo.fX;
|
||
|
IRenderLoop( string, 1 );
|
||
|
}
|
||
|
while( fRenderInfo.fX <= fRenderInfo.fClipRect.fX && *++string != 0 );
|
||
|
fRenderInfo.fMaxWidth += fRenderInfo.fX - prevX;
|
||
|
fRenderInfo.fDestPtr -= (fRenderInfo.fX - prevX) * fRenderInfo.fDestBPP;
|
||
|
fRenderInfo.fX = prevX;
|
||
|
fRenderInfo.fRenderFunc = oldFunc;
|
||
|
}
|
||
|
|
||
|
// There used to be an adjustment of the X coordinate by -ch.fLeftKern for the case
|
||
|
// of kRenderJustXForceLeft here, but it was buggy in that it neglected to adjust
|
||
|
// fRenderInfo.fDestPtr and therefore had no visible effect (or almost none - only
|
||
|
// at the end of the line). Fixing the bug however (making kRenderJustXForceLeft
|
||
|
// work as intended) causes the text shadow to be cut off in some places. To ensure
|
||
|
// enough space for the shadow, and considering that all content was developed and
|
||
|
// visually optimized with the bug in place, it seems better to preserve the buggy
|
||
|
// behavior and make kRenderJustXForceLeft work exactly like kRenderJustXLeft.
|
||
|
|
||
|
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;
|
||
|
|
||
|
(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 );
|
||
|
Int16 x, y, thisHeight, xstart, thisWidth;
|
||
|
UInt32 srcAlpha, oneMinusAlpha, r, g, b, dR, dG, dB, destAlpha;
|
||
|
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 = (Int16)fWidth;// + (Int32)c.fRightKern;
|
||
|
if( thisWidth >= fRenderInfo.fMaxWidth )
|
||
|
thisWidth = fRenderInfo.fMaxWidth;
|
||
|
|
||
|
xstart = fRenderInfo.fClipRect.fX - fRenderInfo.fX;
|
||
|
if( xstart < 0 )
|
||
|
xstart = 0;
|
||
|
|
||
|
srcR = (UInt8)(( fRenderInfo.fColor >> 16 ) & 0x000000ff);
|
||
|
srcG = (UInt8)(( fRenderInfo.fColor >> 8 ) & 0x000000ff);
|
||
|
srcB = (UInt8)(( fRenderInfo.fColor ) & 0x000000ff);
|
||
|
|
||
|
y = fRenderInfo.fClipRect.fY - fRenderInfo.fY + (Int16)c.fBaseline;
|
||
|
if( y < 0 )
|
||
|
y = 0;
|
||
|
else
|
||
|
{
|
||
|
destBasePtr = (UInt32 *)( (UInt8 *)destBasePtr + y*fRenderInfo.fDestStride );
|
||
|
src += y*fRenderInfo.fNumCols;
|
||
|
}
|
||
|
|
||
|
thisHeight = fRenderInfo.fMaxHeight + (Int16)c.fBaseline;
|
||
|
if( thisHeight > (Int16)c.fHeight )
|
||
|
thisHeight = (Int16)c.fHeight;
|
||
|
|
||
|
for( ; y < thisHeight; y++ )
|
||
|
{
|
||
|
destPtr = destBasePtr;
|
||
|
for( x = xstart; 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 );
|
||
|
Int16 x, y, thisHeight, xstart, thisWidth;
|
||
|
UInt32 destColorOnly;
|
||
|
|
||
|
|
||
|
// 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 = (Int16)fWidth;// + (Int32)c.fRightKern;
|
||
|
if( thisWidth >= fRenderInfo.fMaxWidth )
|
||
|
thisWidth = fRenderInfo.fMaxWidth;
|
||
|
|
||
|
xstart = fRenderInfo.fClipRect.fX - fRenderInfo.fX;
|
||
|
if( xstart < 0 )
|
||
|
xstart = 0;
|
||
|
|
||
|
destColorOnly = fRenderInfo.fColor & 0x00ffffff;
|
||
|
|
||
|
y = fRenderInfo.fClipRect.fY - fRenderInfo.fY + (Int16)c.fBaseline;
|
||
|
if( y < 0 )
|
||
|
y = 0;
|
||
|
else
|
||
|
{
|
||
|
destBasePtr = (UInt32 *)( (UInt8 *)destBasePtr + y*fRenderInfo.fDestStride );
|
||
|
src += y*fRenderInfo.fNumCols;
|
||
|
}
|
||
|
|
||
|
thisHeight = fRenderInfo.fMaxHeight + (Int16)c.fBaseline;
|
||
|
if( thisHeight > (Int16)c.fHeight )
|
||
|
thisHeight = (Int16)c.fHeight;
|
||
|
|
||
|
for( ; y < thisHeight; y++ )
|
||
|
{
|
||
|
destPtr = destBasePtr;
|
||
|
for( x = xstart; 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 );
|
||
|
Int16 x, y, thisHeight, xstart, thisWidth;
|
||
|
UInt32 destColorOnly, alphaMult, fullAlpha;
|
||
|
|
||
|
|
||
|
// 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 = (Int16)fWidth;// + (Int32)c.fRightKern;
|
||
|
if( thisWidth >= fRenderInfo.fMaxWidth )
|
||
|
thisWidth = fRenderInfo.fMaxWidth;
|
||
|
|
||
|
xstart = fRenderInfo.fClipRect.fX - fRenderInfo.fX;
|
||
|
if( xstart < 0 )
|
||
|
xstart = 0;
|
||
|
|
||
|
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;
|
||
|
|
||
|
y = fRenderInfo.fClipRect.fY - fRenderInfo.fY + (Int16)c.fBaseline;
|
||
|
if( y < 0 )
|
||
|
y = 0;
|
||
|
else
|
||
|
{
|
||
|
destBasePtr = (UInt32 *)( (UInt8 *)destBasePtr + y*fRenderInfo.fDestStride );
|
||
|
src += y*fRenderInfo.fNumCols;
|
||
|
}
|
||
|
|
||
|
thisHeight = fRenderInfo.fMaxHeight + (Int16)c.fBaseline;
|
||
|
if( thisHeight > (Int16)c.fHeight )
|
||
|
thisHeight = (Int16)c.fHeight;
|
||
|
|
||
|
for( ; y < thisHeight; y++ )
|
||
|
{
|
||
|
destPtr = destBasePtr;
|
||
|
for( x = xstart; 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::IRenderChar8To32AlphaPremultiplied( const plFont::plCharacter &c )
|
||
|
{
|
||
|
UInt8 *src = fBMapData + c.fBitmapOff;
|
||
|
UInt32 *destPtr, *destBasePtr = (UInt32 *)( fRenderInfo.fDestPtr - c.fBaseline * fRenderInfo.fDestStride );
|
||
|
Int16 x, y, thisHeight, xstart, thisWidth;
|
||
|
UInt8 srcA, 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 = (Int16)fWidth;// + (Int32)c.fRightKern;
|
||
|
if( thisWidth >= fRenderInfo.fMaxWidth )
|
||
|
thisWidth = fRenderInfo.fMaxWidth;
|
||
|
|
||
|
xstart = fRenderInfo.fClipRect.fX - fRenderInfo.fX;
|
||
|
if( xstart < 0 )
|
||
|
xstart = 0;
|
||
|
|
||
|
srcA = (UInt8)(( fRenderInfo.fColor >> 24 ) & 0x000000ff);
|
||
|
srcR = (UInt8)(( fRenderInfo.fColor >> 16 ) & 0x000000ff);
|
||
|
srcG = (UInt8)(( fRenderInfo.fColor >> 8 ) & 0x000000ff);
|
||
|
srcB = (UInt8)(( fRenderInfo.fColor ) & 0x000000ff);
|
||
|
|
||
|
y = fRenderInfo.fClipRect.fY - fRenderInfo.fY + (Int16)c.fBaseline;
|
||
|
if( y < 0 )
|
||
|
y = 0;
|
||
|
else
|
||
|
{
|
||
|
destBasePtr = (UInt32 *)( (UInt8 *)destBasePtr + y*fRenderInfo.fDestStride );
|
||
|
src += y*fRenderInfo.fNumCols;
|
||
|
}
|
||
|
|
||
|
thisHeight = fRenderInfo.fMaxHeight + (Int16)c.fBaseline;
|
||
|
if( thisHeight > (Int16)c.fHeight )
|
||
|
thisHeight = (Int16)c.fHeight;
|
||
|
|
||
|
for( ; y < thisHeight; y++ )
|
||
|
{
|
||
|
destPtr = destBasePtr;
|
||
|
for( x = xstart; x < thisWidth; x++ )
|
||
|
{
|
||
|
UInt32 a = src[ x ];
|
||
|
if (a != 0)
|
||
|
{
|
||
|
if (srcA != 0xff)
|
||
|
a = (srcA*a + 127)/255;
|
||
|
destPtr[ x ] = ( a << 24 ) | (((srcR*a + 127)/255) << 16) | (((srcG*a + 127)/255) << 8) | ((srcB*a + 127)/255);
|
||
|
}
|
||
|
}
|
||
|
destBasePtr = (UInt32 *)( (UInt8 *)destBasePtr + fRenderInfo.fDestStride );
|
||
|
src += fWidth;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void plFont::IRenderChar8To32AlphaPremShadow( const plFont::plCharacter &c )
|
||
|
{
|
||
|
UInt32 *destPtr, *destBasePtr = (UInt32 *)( fRenderInfo.fDestPtr - c.fBaseline * fRenderInfo.fDestStride );
|
||
|
Int16 x, y, thisHeight, xstart, thisWidth;
|
||
|
UInt8 srcA, 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 = (Int16)fWidth + 2;// + (Int32)c.fRightKern;
|
||
|
if( thisWidth >= fRenderInfo.fMaxWidth )
|
||
|
thisWidth = fRenderInfo.fMaxWidth;
|
||
|
|
||
|
xstart = fRenderInfo.fClipRect.fX - fRenderInfo.fX;
|
||
|
if( xstart < -2 )
|
||
|
xstart = -2;
|
||
|
|
||
|
srcA = (UInt8)(( fRenderInfo.fColor >> 24 ) & 0x000000ff);
|
||
|
if (srcA == 0)
|
||
|
return;
|
||
|
srcR = (UInt8)(( fRenderInfo.fColor >> 16 ) & 0x000000ff);
|
||
|
srcG = (UInt8)(( fRenderInfo.fColor >> 8 ) & 0x000000ff);
|
||
|
srcB = (UInt8)(( fRenderInfo.fColor ) & 0x000000ff);
|
||
|
|
||
|
static const UInt32 kernel[5][5] = {
|
||
|
{1, 2, 2, 2, 1},
|
||
|
{1, 13, 13, 13, 1},
|
||
|
{1, 10, 10, 10, 1},
|
||
|
{1, 7, 7, 7, 1},
|
||
|
{1, 1, 1, 1, 1}
|
||
|
};
|
||
|
|
||
|
UInt32 clamp = 220 - ((2 * srcR + 4 * srcG + srcB) >> 4);
|
||
|
|
||
|
y = fRenderInfo.fClipRect.fY - fRenderInfo.fY + (Int16)c.fBaseline;
|
||
|
if( y < -2 )
|
||
|
y = -2;
|
||
|
destBasePtr = (UInt32 *)( (UInt8 *)destBasePtr + y*fRenderInfo.fDestStride );
|
||
|
|
||
|
thisHeight = fRenderInfo.fMaxHeight + (Int16)c.fBaseline;
|
||
|
if( thisHeight > (Int16)c.fHeight + 2 )
|
||
|
thisHeight = (Int16)c.fHeight + 2;
|
||
|
|
||
|
for( ; y < thisHeight; y++ )
|
||
|
{
|
||
|
destPtr = destBasePtr;
|
||
|
for( x = xstart; x < thisWidth; x++ )
|
||
|
{
|
||
|
UInt32 sa = 0;
|
||
|
for (Int32 i = -2; i <= 2; i++) {
|
||
|
for (Int32 j = -2; j <= 2; j++) {
|
||
|
UInt32 m = kernel[j+2][i+2];
|
||
|
if (m != 0)
|
||
|
sa += m * IGetCharPixel(c, x+i, y+j);
|
||
|
}
|
||
|
}
|
||
|
sa = (sa * clamp) >> 13;
|
||
|
if (sa > clamp)
|
||
|
sa = clamp;
|
||
|
UInt32 a = IGetCharPixel(c, x, y);
|
||
|
if (srcA != 0xff)
|
||
|
{
|
||
|
a = (srcA * a + 127) / 255;
|
||
|
sa = (srcA * sa + 127) / 255;
|
||
|
}
|
||
|
UInt32 ta = a + sa - ((a*sa + 127) / 255);
|
||
|
if (ta > (destPtr[ x ] >> 24))
|
||
|
destPtr[ x ] = ( ta << 24 ) | (((srcR * a + 127) / 255) << 16) |
|
||
|
(((srcG * a + 127) / 255) << 8) |
|
||
|
(((srcB * a + 127) / 255) << 0);
|
||
|
}
|
||
|
destBasePtr = (UInt32 *)( (UInt8 *)destBasePtr + fRenderInfo.fDestStride );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
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;
|
||
|
}
|