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.
1982 lines
57 KiB
1982 lines
57 KiB
/*==LICENSE==* |
|
|
|
CyanWorlds.com Engine - MMOG client, server and tools |
|
Copyright (C) 2011 Cyan Worlds, Inc. |
|
|
|
This program is free software: you can redistribute it and/or modify |
|
it under the terms of the GNU General Public License as published by |
|
the Free Software Foundation, either version 3 of the License, or |
|
(at your option) any later version. |
|
|
|
This program is distributed in the hope that it will be useful, |
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
GNU General Public License for more details. |
|
|
|
You should have received a copy of the GNU General Public License |
|
along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
|
|
Additional permissions under GNU GPL version 3 section 7 |
|
|
|
If you modify this Program, or any covered work, by linking or |
|
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, |
|
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent |
|
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK |
|
(or a modified version of those libraries), |
|
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, |
|
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG |
|
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the |
|
licensors of this Program grant you additional |
|
permission to convey the resulting work. Corresponding Source for a |
|
non-source form of such a combination shall include the source code for |
|
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered |
|
work. |
|
|
|
You can contact Cyan Worlds, Inc. by email legal@cyan.com |
|
or by snail mail at: |
|
Cyan Worlds, Inc. |
|
14617 N Newport Hwy |
|
Mead, WA 99021 |
|
|
|
*==LICENSE==*/ |
|
/////////////////////////////////////////////////////////////////////////////// |
|
// // |
|
// plFont Class Header // |
|
// Seems like we've come full circle again. This is our generic Plasma // |
|
// bitmap font class/format. Quick list of things it supports, or needs to: // |
|
// - Antialiasing, either in the font def or at rendertime // |
|
// - Doublebyte character sets // |
|
// - Platform independence, of course // |
|
// - Render to reasonably arbitrary mipmap // |
|
// - Character-level kerning, both before and after, as well as // |
|
// negative kerning (for ligatures) // |
|
// // |
|
// Cyan, Inc. // |
|
// // |
|
//// Version History ////////////////////////////////////////////////////////// |
|
// // |
|
// 3.4.2003 mcn - Created. // |
|
// 4.3.2003 mcn - Updated. Casting a char to a UInt16 sign-extends it if // |
|
// the char is > 128, but casting it to an UInt8 first works.// |
|
// Ugly as sin, but hey, so are you. // |
|
// // |
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
|
#include "hsTypes.h" |
|
#include "hsStlUtils.h" |
|
#include "plFont.h" |
|
|
|
#include "plMipmap.h" |
|
#include "hsResMgr.h" |
|
|
|
|
|
//// plCharacter Stuff //////////////////////////////////////////////////////// |
|
|
|
plFont::plCharacter::plCharacter() |
|
{ |
|
fBitmapOff = 0; |
|
fHeight = 0; |
|
fBaseline = 0; |
|
fLeftKern = fRightKern = 0.f; |
|
} |
|
|
|
void plFont::plCharacter::Read( hsStream *s ) |
|
{ |
|
// Rocket science here... |
|
s->ReadSwap( &fBitmapOff ); |
|
s->ReadSwap( &fHeight ); |
|
s->ReadSwap( &fBaseline ); |
|
s->ReadSwap( &fLeftKern ); |
|
s->ReadSwap( &fRightKern ); |
|
} |
|
|
|
void plFont::plCharacter::Write( hsStream *s ) |
|
{ |
|
s->WriteSwap( fBitmapOff ); |
|
s->WriteSwap( fHeight ); |
|
s->WriteSwap( fBaseline ); |
|
s->WriteSwap( fLeftKern ); |
|
s->WriteSwap( fRightKern ); |
|
} |
|
|
|
//// Constructor/Read/Write/Destructor/etc //////////////////////////////////// |
|
|
|
plFont::plFont() |
|
{ |
|
IClear( true ); |
|
} |
|
|
|
plFont::~plFont() |
|
{ |
|
IClear(); |
|
} |
|
|
|
void plFont::IClear( bool onConstruct ) |
|
{ |
|
if( !onConstruct ) |
|
delete [] fBMapData; |
|
|
|
memset( fFace, 0, sizeof( fFace ) ); |
|
fSize = 0; |
|
fFlags = 0; |
|
|
|
fWidth = fHeight = 0; |
|
fBPP = 0; |
|
fBMapData = nil; |
|
fFirstChar = 0; |
|
fMaxCharHeight = 0; |
|
fCharacters.Reset(); |
|
|
|
fRenderInfo.fFlags = 0; |
|
fRenderInfo.fX = fRenderInfo.fY = fRenderInfo.fNumCols = 0; |
|
fRenderInfo.fMaxWidth = fRenderInfo.fMaxHeight = 0; |
|
fRenderInfo.fDestPtr = nil; |
|
fRenderInfo.fDestStride = 0; |
|
fRenderInfo.fColor = 0; |
|
fRenderInfo.fMipmap = nil; |
|
fRenderInfo.fRenderFunc = nil; |
|
fRenderInfo.fVolatileStringPtr = nil; |
|
fRenderInfo.fFirstLineIndent = 0; |
|
fRenderInfo.fLineSpacing = 0; |
|
} |
|
|
|
void plFont::SetFace( const char *face ) |
|
{ |
|
strncpy( fFace, face, sizeof( fFace ) ); |
|
} |
|
|
|
void plFont::SetSize( UInt8 size ) |
|
{ |
|
fSize = size; |
|
} |
|
|
|
void plFont::Read( hsStream *s, hsResMgr *mgr ) |
|
{ |
|
hsKeyedObject::Read( s, mgr ); |
|
ReadRaw( s ); |
|
} |
|
|
|
void plFont::Write( hsStream *s, hsResMgr *mgr ) |
|
{ |
|
hsKeyedObject::Write( s, mgr ); |
|
WriteRaw( s ); |
|
} |
|
|
|
/////////////////////////////////////////////////////////////////////////////// |
|
//// Rendering //////////////////////////////////////////////////////////////// |
|
/////////////////////////////////////////////////////////////////////////////// |
|
|
|
void plFont::SetRenderColor( UInt32 color ) |
|
{ |
|
fRenderInfo.fColor = color; |
|
} |
|
|
|
void plFont::SetRenderClipRect( Int16 x, Int16 y, Int16 width, Int16 height ) |
|
{ |
|
fRenderInfo.fClipRect.Set( x, y, width, height ); |
|
} |
|
|
|
void plFont::SetRenderClipping( Int16 x, Int16 y, Int16 width, Int16 height ) |
|
{ |
|
SetRenderFlag( kRenderWrap, false ); |
|
SetRenderFlag( kRenderClip, true ); |
|
SetRenderClipRect( x, y, width, height ); |
|
} |
|
|
|
void plFont::SetRenderWrapping( Int16 x, Int16 y, Int16 width, Int16 height ) |
|
{ |
|
SetRenderFlag( kRenderWrap, true ); |
|
SetRenderFlag( kRenderClip, false ); |
|
SetRenderClipRect( x, y, width, height ); |
|
} |
|
|
|
void plFont::ICalcFontAscent( void ) |
|
{ |
|
UInt32 i; |
|
|
|
|
|
// Hack for now, only calc the ascent for characters in the 127 character ASCII range |
|
for( i = 0, fFontAscent = 0, fFontDescent = 0, fMaxCharHeight = 0; i < fCharacters.GetCount(); i++ ) |
|
{ |
|
if( i + fFirstChar < 128 && fFontAscent < fCharacters[ i ].fBaseline ) |
|
fFontAscent = fCharacters[ i ].fBaseline; |
|
|
|
Int32 descent = fCharacters[ i ].fHeight - fCharacters[ i ].fBaseline; |
|
if( fFontDescent < descent ) |
|
fFontDescent = descent; |
|
|
|
if( fMaxCharHeight < fCharacters[ i ].fHeight ) |
|
fMaxCharHeight = fCharacters[ i ].fHeight; |
|
} |
|
} |
|
|
|
//// IIsWordBreaker ////////////////////////////////////////////////////////// |
|
// Returns whether the given character is one that can break a line |
|
|
|
static inline bool IIsWordBreaker( const char c ) |
|
{ |
|
return ( strchr( " \t,.;\n", c ) != nil ) ? true : false; |
|
} |
|
|
|
//// IIsDrawableWordBreak ///////////////////////////////////////////////////// |
|
// Returns whether the given character is a line breaker that has to be drawn |
|
// (non whitespace) |
|
|
|
static inline bool IIsDrawableWordBreak( const char c ) |
|
{ |
|
return ( strchr( ",.;", c ) != nil ) ? true : false; |
|
} |
|
|
|
//// RenderString ///////////////////////////////////////////////////////////// |
|
// The base render function. Additional options are specified externally, |
|
// so that their effects can be cached for optimization |
|
|
|
void plFont::RenderString( plMipmap *mip, UInt16 x, UInt16 y, const char *string, UInt16 *lastX, UInt16 *lastY ) |
|
{ |
|
// convert the char string to a wchar_t string |
|
wchar_t *wideString = hsStringToWString(string); |
|
RenderString(mip,x,y,wideString,lastX,lastY); |
|
delete [] wideString; |
|
} |
|
|
|
|
|
void plFont::RenderString( plMipmap *mip, UInt16 x, UInt16 y, const wchar_t *string, UInt16 *lastX, UInt16 *lastY ) |
|
{ |
|
if( mip->IsCompressed() ) |
|
{ |
|
hsAssert( false, "Unable to render string to compressed mipmap" ); |
|
return; |
|
} |
|
|
|
IRenderString( mip, x, y, string, false ); |
|
if( lastX != nil ) |
|
*lastX = fRenderInfo.fLastX; |
|
if( lastY != nil ) |
|
*lastY = fRenderInfo.fLastY; |
|
} |
|
|
|
void plFont::IRenderString( plMipmap *mip, UInt16 x, UInt16 y, const wchar_t *string, hsBool justCalc ) |
|
{ |
|
fRenderInfo.fMipmap = mip; |
|
fRenderInfo.fX = x; |
|
fRenderInfo.fY = y; |
|
fRenderInfo.fNumCols = (Int16)(( fBPP <= 8 ) ? ( ( fWidth * fBPP ) >> 3 ) : 0); |
|
fRenderInfo.fFloatWidth = (hsScalar)fWidth; |
|
fRenderInfo.fFarthestX = x; |
|
fRenderInfo.fMaxAscent = 0; |
|
fRenderInfo.fVolatileStringPtr = string; |
|
|
|
switch( fRenderInfo.fFlags & kRenderJustYMask ) |
|
{ |
|
case kRenderJustYTop: |
|
fRenderInfo.fY += (Int16)fFontAscent; |
|
break; |
|
case kRenderJustYBottom: |
|
if( fRenderInfo.fFlags & ( kRenderClip | kRenderWrap ) ) |
|
fRenderInfo.fY = (Int16)(fRenderInfo.fClipRect.GetBottom() - 1 - fMaxCharHeight + fFontAscent); |
|
else |
|
fRenderInfo.fY = (Int16)(mip->GetHeight() - 1 - fMaxCharHeight + fFontAscent); |
|
break; |
|
case kRenderJustYCenter: |
|
fRenderInfo.fY = (Int16)(( fRenderInfo.fFlags & ( kRenderClip | kRenderWrap ) ) ? fRenderInfo.fClipRect.GetBottom() - 1 : mip->GetHeight() - 1); |
|
fRenderInfo.fY = (Int16)(( fRenderInfo.fY - fMaxCharHeight ) >> 1); |
|
fRenderInfo.fY += (Int16)fFontAscent; |
|
break; |
|
default: // Just the baseline |
|
; |
|
} |
|
|
|
if( justCalc ) |
|
{ |
|
plCharacter &ch = fCharacters[ (UInt16)string[ 0 ] - fFirstChar ]; |
|
fRenderInfo.fX = fRenderInfo.fFarthestX = x - (Int16)ch.fLeftKern; |
|
if( fRenderInfo.fX < 0 ) |
|
fRenderInfo.fX = 0; |
|
} |
|
else |
|
{ |
|
switch( fRenderInfo.fFlags & kRenderJustXMask ) |
|
{ |
|
case kRenderJustXLeft: |
|
// Default |
|
break; |
|
case kRenderJustXForceLeft: |
|
{ |
|
// plCharacter &ch = fCharacters[ (UInt16)(UInt8)string[ 0 ] - fFirstChar ]; |
|
// Int32 newX = x - (Int16)ch.fLeftKern; |
|
// if( newX < 0 ) |
|
// newX = 0; |
|
// fRenderInfo.fX = fRenderInfo.fFarthestX = newX; |
|
} |
|
break; |
|
case kRenderJustXCenter: |
|
{ |
|
UInt16 right = (UInt16)(( fRenderInfo.fFlags & ( kRenderClip | kRenderWrap ) ) ? fRenderInfo.fClipRect.GetRight() : mip->GetWidth()); |
|
// UInt16 width = CalcStringWidth( string ); |
|
fRenderInfo.fX = fRenderInfo.fFarthestX = ( ( x + right ) >> 1 );// - ( width >> 1 ); |
|
} |
|
break; |
|
case kRenderJustXRight: |
|
{ |
|
UInt16 width = 0, right = (UInt16)(( fRenderInfo.fFlags & ( kRenderClip | kRenderWrap ) ) ? fRenderInfo.fClipRect.GetRight() : mip->GetWidth()); |
|
/* if( fRenderInfo.fFlags & kRenderClip ) |
|
{ |
|
fRenderInfo.fFlags &= ~kRenderClip; |
|
width = CalcStringWidth( string ); |
|
fRenderInfo.fFlags |= kRenderClip; |
|
} |
|
else |
|
*/ width = 0;//CalcStringWidth( string ); |
|
fRenderInfo.fX = fRenderInfo.fFarthestX = right - width; |
|
} |
|
break; |
|
} |
|
} |
|
|
|
// Choose an optimal rendering function |
|
fRenderInfo.fRenderFunc = nil; |
|
if( justCalc ) |
|
fRenderInfo.fRenderFunc = IRenderCharNull; |
|
else if( mip->GetPixelSize() == 32 ) |
|
{ |
|
if( fBPP == 1 ) |
|
fRenderInfo.fRenderFunc = ( fRenderInfo.fFlags & kRenderScaleAA ) ? IRenderChar1To32AA : IRenderChar1To32; |
|
else if( fBPP == 8 ) |
|
{ |
|
if( fRenderInfo.fFlags & kRenderIntoAlpha ) |
|
{ |
|
if( ( fRenderInfo.fColor & 0xff000000 ) != 0xff000000 ) |
|
fRenderInfo.fRenderFunc = IRenderChar8To32Alpha; |
|
else |
|
fRenderInfo.fRenderFunc = IRenderChar8To32FullAlpha; |
|
} |
|
else |
|
fRenderInfo.fRenderFunc = IRenderChar8To32; |
|
} |
|
} |
|
|
|
if( fRenderInfo.fRenderFunc == nil ) |
|
{ |
|
hsAssert( false, "Invalid combination of source and destination formats in RenderString()" ); |
|
return; |
|
} |
|
|
|
// Init our other render values |
|
if( !justCalc ) |
|
{ |
|
fRenderInfo.fDestStride = mip->GetRowBytes(); |
|
fRenderInfo.fDestBPP = mip->GetPixelSize() >> 3; |
|
fRenderInfo.fDestPtr = (UInt8 *)mip->GetImage(); |
|
fRenderInfo.fDestPtr += fRenderInfo.fY * fRenderInfo.fDestStride; |
|
fRenderInfo.fDestPtr += fRenderInfo.fX * fRenderInfo.fDestBPP; |
|
} |
|
if( fRenderInfo.fFlags & ( kRenderWrap | kRenderClip ) ) |
|
{ |
|
fRenderInfo.fMaxHeight = (Int16)( fRenderInfo.fClipRect.GetBottom() - fRenderInfo.fY ); |
|
fRenderInfo.fMaxWidth = (Int16)( fRenderInfo.fClipRect.GetRight() - x ); |
|
} |
|
else if( justCalc ) |
|
{ |
|
// Just calculating, no wrapping, so the max is as far as we can go |
|
// Note: 32767 isn't quite right, since we'll be adding the left kern in before we |
|
// calc the first character, so adjust so we make sure we don't underflow |
|
plCharacter &ch = fCharacters[ (UInt16)(UInt8)string[ 0 ] - fFirstChar ]; |
|
|
|
fRenderInfo.fMaxHeight = (Int16)fMaxCharHeight; |
|
fRenderInfo.fMaxWidth = (Int16)32767 + (Int16)ch.fLeftKern; |
|
} |
|
else |
|
{ |
|
fRenderInfo.fMaxHeight = (Int16)( mip->GetHeight() - fRenderInfo.fY ); |
|
fRenderInfo.fMaxWidth = (Int16)( mip->GetWidth() - x ); |
|
} |
|
|
|
fRenderInfo.fMaxDescent = 0; |
|
|
|
if( fRenderInfo.fFlags & kRenderWrap ) |
|
{ |
|
// Hell, gotta word wrap the text |
|
// To avoid backtracking, we step forward in the string one word at a time until we hit a break, |
|
// then render what we have and continue |
|
Int32 lastWord = 0, i; |
|
bool isFirstLine = true; |
|
x = 0; |
|
Int16 firstMaxAscent = 0; |
|
UInt32 lineHt, lineDelta; |
|
if( fRenderInfo.fFlags & kRenderScaleAA ) |
|
lineHt = fMaxCharHeight >> 1; |
|
else |
|
lineHt = fMaxCharHeight; |
|
|
|
// adjust the line height for spacing |
|
lineHt += fRenderInfo.fLineSpacing; |
|
|
|
lineDelta = lineHt * fRenderInfo.fDestStride; |
|
|
|
while( *string != 0 && fRenderInfo.fMaxHeight >= fFontDescent ) |
|
{ |
|
UInt8 *destStartPtr = fRenderInfo.fDestPtr; |
|
UInt32 destStartX = fRenderInfo.fX; |
|
Int16 destMaxWidth = fRenderInfo.fMaxWidth, thisIndent = 0; |
|
|
|
if( isFirstLine ) |
|
{ |
|
// First line, apply indent if applicable |
|
fRenderInfo.fX += fRenderInfo.fFirstLineIndent; |
|
fRenderInfo.fMaxWidth -= fRenderInfo.fFirstLineIndent; |
|
fRenderInfo.fDestPtr += fRenderInfo.fFirstLineIndent * fRenderInfo.fDestBPP; |
|
thisIndent = fRenderInfo.fFirstLineIndent; |
|
isFirstLine = false; |
|
} |
|
|
|
std::string ellipsisTracker = ""; // keeps track of ellipsis, since there are three word break chars that can't be split |
|
bool possibleEllipsis = false; |
|
int preEllipsisLastWord = 0; // where the word break was before we started tracking an ellipsis |
|
|
|
// Iterate through the string, looking for the next line break |
|
for( lastWord = 0, i = 0; string[ i ] != 0; i++ ) |
|
{ |
|
// If we're a carriage return, we go ahead and break anyway |
|
if( string[ i ] == L'\n' ) |
|
{ |
|
lastWord = i; |
|
break; |
|
} |
|
|
|
// handle invalid chars discretely |
|
plCharacter* charToDraw = NULL; |
|
if (fCharacters.Count() <= ((UInt16)string[i] - fFirstChar)) |
|
charToDraw = &(fCharacters[(UInt16)L' ' - fFirstChar]); |
|
else |
|
charToDraw = &(fCharacters[(UInt16)string[i] - fFirstChar]); |
|
|
|
Int16 leftKern = (Int16)charToDraw->fLeftKern; |
|
if( fRenderInfo.fFlags & kRenderScaleAA ) |
|
x += leftKern / 2; |
|
else |
|
x += leftKern; |
|
|
|
// Update our position and see if we're over |
|
// Note that our wrapping is slightly off, in that it doesn't take into account |
|
// the left kerning of characters. Hopefully that won't matter much... |
|
UInt16 charWidth = (UInt16)(fWidth + (Int16)charToDraw->fRightKern); |
|
if( fRenderInfo.fFlags & kRenderScaleAA ) |
|
charWidth >>= 1; |
|
|
|
UInt16 nonAdjustedX = (UInt16)(x + fWidth); // just in case the actual bitmap is too big to fit on page and we think the character can (because of right kern) |
|
x += charWidth; |
|
|
|
if(( x >= fRenderInfo.fMaxWidth ) || (nonAdjustedX >= fRenderInfo.fMaxWidth)) |
|
{ |
|
// we're over, but lastWord may not be correct (especially if we're in the middle of an ellipsis) |
|
if (possibleEllipsis) |
|
{ |
|
// ellipsisTracker will not be empty since possibleEllipsis is true (so there will be at least one period) |
|
if (ellipsisTracker == ".") // only one period so far |
|
{ |
|
if ((string[i] == '.') && (string[i+1] == '.')) // we have an ellipsis, so reset the lastWord back before we found it |
|
lastWord = preEllipsisLastWord; |
|
// otherwise, we don't have an ellipsis, so lastWord is correct (but the grammer might not be ;-) |
|
} |
|
else if (ellipsisTracker == "..") // only two periods so far |
|
{ |
|
if (string[i] == '.') // we have an ellipsis, so reset the lastWord back before we found it |
|
lastWord = preEllipsisLastWord; |
|
// otherwise, we don't have an ellipsis, so lastWord is correct (but the grammer might not be ;-) |
|
} |
|
// if neither of the above are true, then the full ellipsis was encountered and the lastWord is correct |
|
ellipsisTracker = ""; |
|
possibleEllipsis = false; |
|
} |
|
// Over, so break |
|
break; |
|
} |
|
|
|
// Are we a word breaker? |
|
if( IIsWordBreaker( (char)(string[ i ]) ) ) |
|
{ |
|
if (string[i] == '.') // we might have an ellipsis here |
|
{ |
|
if (ellipsisTracker == "...") // we already have a full ellipsis, so break between them |
|
{ |
|
preEllipsisLastWord = i; |
|
ellipsisTracker = ""; |
|
} |
|
else if (ellipsisTracker == "") // no ellipsis yet, so save the last word |
|
preEllipsisLastWord = lastWord; |
|
ellipsisTracker += '.'; |
|
possibleEllipsis = true; |
|
} |
|
else |
|
{ |
|
ellipsisTracker = ""; // no chance of an ellipsis, so kill it |
|
possibleEllipsis = false; |
|
} |
|
// Yes, and we didn't go over, so store as the last successfully fit word and move on |
|
lastWord = i; |
|
} |
|
} |
|
|
|
if( string[ i ] == 0 ) |
|
lastWord = i; // Final catch for end-of-string |
|
else if( lastWord == 0 && string[ i ] != L'\n' && thisIndent == 0 ) |
|
lastWord = i; // Catch for a single word that didn't fit (just go up as many chars as we can) |
|
// (but NOT if we have a first line indent, mind you :) |
|
|
|
// Got to the end of a line (somewhere), so render up to lastWord, then advance from that point |
|
// to the first non-word-breaker and continue onward |
|
if( lastWord > 0 ) |
|
{ |
|
bool drawableWordBreak = IIsDrawableWordBreak((char)(string[lastWord])); |
|
if (drawableWordBreak) |
|
lastWord++; // make sure we draw it |
|
if( ( fRenderInfo.fFlags & kRenderJustXMask ) == kRenderJustXRight ) |
|
{ |
|
UInt16 baseX = fRenderInfo.fX, baseMaxW = fRenderInfo.fMaxWidth; |
|
UInt8 *baseDestPtr = fRenderInfo.fDestPtr; |
|
|
|
fRenderInfo.fX = 0; |
|
CharRenderFunc oldFunc = fRenderInfo.fRenderFunc; |
|
fRenderInfo.fRenderFunc = IRenderCharNull; |
|
|
|
IRenderLoop( string, lastWord ); |
|
|
|
fRenderInfo.fRenderFunc = oldFunc; |
|
fRenderInfo.fX = baseX - fRenderInfo.fX; |
|
fRenderInfo.fMaxWidth = baseMaxW; |
|
fRenderInfo.fDestPtr = baseDestPtr + ( fRenderInfo.fX - baseX ) * fRenderInfo.fDestBPP; |
|
|
|
IRenderLoop( string, lastWord ); |
|
|
|
fRenderInfo.fLastX = fRenderInfo.fX; |
|
fRenderInfo.fLastY = fRenderInfo.fY; |
|
fRenderInfo.fX = baseX; |
|
} |
|
else if( ( fRenderInfo.fFlags & kRenderJustXMask ) == kRenderJustXCenter ) |
|
{ |
|
UInt16 baseX = fRenderInfo.fX, baseMaxW = fRenderInfo.fMaxWidth; |
|
UInt8 *baseDestPtr = fRenderInfo.fDestPtr; |
|
|
|
fRenderInfo.fX = 0; |
|
CharRenderFunc oldFunc = fRenderInfo.fRenderFunc; |
|
fRenderInfo.fRenderFunc = IRenderCharNull; |
|
|
|
IRenderLoop( string, lastWord ); |
|
|
|
fRenderInfo.fRenderFunc = oldFunc; |
|
fRenderInfo.fX = baseX - ( fRenderInfo.fX >> 1 ); |
|
fRenderInfo.fMaxWidth = baseMaxW; |
|
fRenderInfo.fDestPtr = baseDestPtr + ( fRenderInfo.fX - baseX ) * fRenderInfo.fDestBPP; |
|
|
|
IRenderLoop( string, lastWord ); |
|
|
|
fRenderInfo.fLastX = fRenderInfo.fX; |
|
fRenderInfo.fLastY = fRenderInfo.fY; |
|
fRenderInfo.fX = baseX; |
|
} |
|
else if( ( fRenderInfo.fFlags & kRenderJustXMask ) == kRenderJustXForceLeft ) |
|
{ |
|
Int16 baseX = fRenderInfo.fX; |
|
|
|
plCharacter &ch = fCharacters[ (UInt16)string[ 0 ] - fFirstChar ]; |
|
|
|
fRenderInfo.fX -= (Int16)ch.fLeftKern; |
|
fRenderInfo.fDestPtr -= (Int16)ch.fLeftKern * fRenderInfo.fDestBPP; |
|
|
|
IRenderLoop( string, lastWord ); |
|
|
|
fRenderInfo.fLastX = fRenderInfo.fX; |
|
fRenderInfo.fLastY = fRenderInfo.fY; |
|
fRenderInfo.fX = baseX; |
|
} |
|
else |
|
{ |
|
IRenderLoop( string, lastWord ); |
|
fRenderInfo.fLastX = fRenderInfo.fX; |
|
fRenderInfo.fLastY = fRenderInfo.fY; |
|
} |
|
if (drawableWordBreak) |
|
lastWord--; // reset it |
|
} |
|
|
|
|
|
// Store farthest X for later reference |
|
if( lastWord > 0 && fRenderInfo.fX > fRenderInfo.fFarthestX ) |
|
fRenderInfo.fFarthestX = fRenderInfo.fX; |
|
|
|
if( firstMaxAscent == 0 ) |
|
firstMaxAscent = fRenderInfo.fMaxAscent; |
|
|
|
x = 0; |
|
fRenderInfo.fX = (Int16)destStartX; |
|
fRenderInfo.fDestPtr = destStartPtr; |
|
fRenderInfo.fMaxWidth = destMaxWidth; |
|
fRenderInfo.fMaxAscent = 0; |
|
|
|
// Look for the next non-word-breaker. Note that if we have any carriage returns hidden in here, |
|
// we'll want to be advancing down even further |
|
// Advance down |
|
if( string[ i ] != 0 ) |
|
{ |
|
fRenderInfo.fY += (Int16)lineHt; |
|
fRenderInfo.fMaxHeight -= (Int16)lineHt; |
|
fRenderInfo.fDestPtr += lineDelta; |
|
fRenderInfo.fLastX = fRenderInfo.fX; |
|
fRenderInfo.fLastY = fRenderInfo.fY; |
|
} |
|
for( i = lastWord; string[ i ] != 0 && IIsWordBreaker( (char)(string[ i ]) ) && string[ i ] != L'\n'; i++ ) |
|
{ |
|
} |
|
// Process any trailing carriage returns as a separate loop b/c we don't want to throw away white space |
|
// after returns |
|
for( ; string[ i ] == L'\n'; i++ ) |
|
{ |
|
// Don't process if i==lastWord, since we already did that one |
|
if( i > lastWord && string[ i ] == L'\n' ) |
|
{ |
|
// Advance down |
|
fRenderInfo.fY += (Int16)lineHt; |
|
fRenderInfo.fMaxHeight -= (Int16)lineHt; |
|
fRenderInfo.fDestPtr += lineDelta; |
|
fRenderInfo.fLastY = fRenderInfo.fY; |
|
} |
|
} |
|
|
|
// Keep going from here! |
|
string += i; |
|
} |
|
|
|
fRenderInfo.fMaxAscent = firstMaxAscent; |
|
// Final y offset from the last line |
|
fRenderInfo.fY += (Int16)fFontDescent; |
|
|
|
fRenderInfo.fVolatileStringPtr = string; |
|
} |
|
else |
|
{ |
|
if( fRenderInfo.fFlags & kRenderClip ) |
|
{ |
|
// Advance left past any clipping area |
|
CharRenderFunc oldFunc = fRenderInfo.fRenderFunc; |
|
fRenderInfo.fRenderFunc = IRenderCharNull; |
|
while( fRenderInfo.fX < fRenderInfo.fClipRect.fX && *string != 0 ) |
|
{ |
|
IRenderLoop( string, 1 ); |
|
string++; |
|
} |
|
fRenderInfo.fRenderFunc = oldFunc; |
|
} |
|
|
|
// Adjust for left kern |
|
if( ( fRenderInfo.fFlags & kRenderJustXMask ) == kRenderJustXForceLeft ) |
|
{ |
|
// See note at top of file |
|
plCharacter &ch = fCharacters[ (UInt16)string[ 0 ] - fFirstChar ]; |
|
Int32 newX = x - (Int16)ch.fLeftKern; |
|
if( newX < 0 ) |
|
newX = 0; |
|
fRenderInfo.fX = fRenderInfo.fFarthestX = (Int16)newX; |
|
} |
|
|
|
fRenderInfo.fVolatileStringPtr = string; // Just so we can keep track of when we clip |
|
IRenderLoop( string, -1 ); |
|
fRenderInfo.fFarthestX = fRenderInfo.fX; |
|
} |
|
} |
|
|
|
void plFont::IRenderLoop( const wchar_t *string, Int32 maxCount ) |
|
{ |
|
// Render the string straight across, one char at a time |
|
while( *string != 0 && maxCount != 0 ) // Note: if maxCount starts out -1, then it'll just keep |
|
{ // decrementing...well ok, not for forever, but pretty close |
|
UInt16 c = (UInt16)*string; |
|
if( c < fFirstChar ) |
|
; // Invalid char |
|
else |
|
{ |
|
c -= fFirstChar; |
|
if( c >= fCharacters.GetCount() ) |
|
; // Invalid char |
|
else |
|
{ |
|
// First pass at supporting left kerning values, but only at the pixel level |
|
Int16 leftKern = (Int16)fCharacters[ c ].fLeftKern; |
|
if( leftKern != 0 ) |
|
{ |
|
if( fRenderInfo.fFlags & kRenderScaleAA ) |
|
leftKern /= 2; |
|
|
|
fRenderInfo.fX += leftKern; |
|
fRenderInfo.fMaxWidth -= leftKern; |
|
fRenderInfo.fDestPtr += leftKern * fRenderInfo.fDestBPP; |
|
} |
|
|
|
UInt16 thisWidth = (UInt16)(fWidth + fCharacters[ c ].fRightKern); |
|
if( fRenderInfo.fFlags & kRenderScaleAA ) |
|
thisWidth >>= 1; |
|
|
|
if( thisWidth >= fRenderInfo.fMaxWidth ) |
|
break; |
|
|
|
(this->*(fRenderInfo.fRenderFunc))( fCharacters[ c ] ); |
|
|
|
fRenderInfo.fX += thisWidth; |
|
fRenderInfo.fMaxWidth -= thisWidth; |
|
fRenderInfo.fDestPtr += thisWidth * fRenderInfo.fDestBPP; |
|
|
|
|
|
Int16 baseline = (Int16)(fCharacters[ c ].fBaseline); |
|
if( fRenderInfo.fFlags & kRenderScaleAA ) |
|
baseline >>= 1; |
|
|
|
if( baseline > fRenderInfo.fMaxAscent ) |
|
fRenderInfo.fMaxAscent = baseline; |
|
|
|
Int16 thisHt = (Int16)(fCharacters[ c ].fHeight - baseline); |
|
if( fRenderInfo.fMaxDescent < thisHt ) |
|
fRenderInfo.fMaxDescent = thisHt; |
|
} |
|
} |
|
|
|
string++; |
|
fRenderInfo.fVolatileStringPtr++; |
|
maxCount--; |
|
} |
|
} |
|
|
|
//// The Rendering Functions ////////////////////////////////////////////////// |
|
|
|
void plFont::IRenderChar1To32( const plFont::plCharacter &c ) |
|
{ |
|
UInt8 bitMask, *src = fBMapData + c.fBitmapOff; |
|
UInt32 *destPtr, *destBasePtr = (UInt32 *)( fRenderInfo.fDestPtr - c.fBaseline * fRenderInfo.fDestStride ); |
|
UInt16 x, y; |
|
|
|
|
|
if( (Int32)c.fHeight - (Int32)c.fBaseline >= fRenderInfo.fMaxHeight || c.fBaseline > fRenderInfo.fY ) |
|
return; |
|
|
|
for( y = 0; y < c.fHeight; y++ ) |
|
{ |
|
destPtr = destBasePtr; |
|
if( fRenderInfo.fFlags & kRenderItalic ) |
|
{ |
|
// Faux italic |
|
destPtr += ( c.fHeight - y ) >> 1; |
|
} |
|
|
|
for( x = 0; x < fRenderInfo.fNumCols; x++ ) |
|
{ |
|
for( bitMask = 0x80; bitMask != 0; bitMask >>= 1 ) |
|
{ |
|
if( *src & bitMask ) |
|
*destPtr = fRenderInfo.fColor; |
|
destPtr++; |
|
} |
|
src++; |
|
} |
|
destBasePtr = (UInt32 *)( (UInt8 *)destBasePtr + fRenderInfo.fDestStride ); |
|
} |
|
} |
|
|
|
void plFont::IRenderChar1To32AA( const plFont::plCharacter &c ) |
|
{ |
|
UInt8 bitMask, *src = fBMapData + c.fBitmapOff; |
|
UInt32 *destPtr, *destBasePtr = (UInt32 *)( fRenderInfo.fDestPtr - ( c.fBaseline >> 1 ) * fRenderInfo.fDestStride ); |
|
UInt16 x, y; |
|
|
|
|
|
if( ( ( (Int32)c.fHeight - (Int32)c.fBaseline ) >> 1 ) >= fRenderInfo.fMaxHeight || ( c.fBaseline >> 1 ) > fRenderInfo.fY ) |
|
return; |
|
|
|
for( y = 0; y < c.fHeight; y += 2 ) |
|
{ |
|
destPtr = destBasePtr; |
|
if( fRenderInfo.fFlags & kRenderItalic ) |
|
{ |
|
// Faux italic |
|
destPtr += ( c.fHeight - y ) >> 2; |
|
} |
|
|
|
for( x = 0; x < fRenderInfo.fNumCols; x++ ) |
|
{ |
|
for( bitMask = 0x80; bitMask != 0; bitMask >>= 2 ) |
|
{ |
|
// Grab 4 bits and do 4-to-1 AA |
|
UInt8 value = ( *src & bitMask ) ? 1 : 0; |
|
value += ( *src & ( bitMask >> 1 ) ) ? 1 : 0; |
|
value += ( src[ fRenderInfo.fNumCols ] & bitMask ) ? 1 : 0; |
|
value += ( src[ fRenderInfo.fNumCols ] & ( bitMask >> 1 ) ) ? 1 : 0; |
|
|
|
switch( value ) |
|
{ |
|
case 1: |
|
{ |
|
UInt32 src = ( fRenderInfo.fColor >> 2 ) & 0x3f3f3f3f; |
|
UInt32 dst = ( (*destPtr) >> 2 ) & 0x3f3f3f3f; |
|
*destPtr = src + dst + dst + dst; |
|
break; |
|
} |
|
case 2: |
|
{ |
|
UInt32 src = ( fRenderInfo.fColor >> 1 ) & 0x7f7f7f7f; |
|
UInt32 dst = ( (*destPtr) >> 1 ) & 0x7f7f7f7f; |
|
*destPtr = src + dst; |
|
break; |
|
} |
|
case 3: |
|
{ |
|
UInt32 src = ( fRenderInfo.fColor >> 2 ) & 0x3f3f3f3f; |
|
UInt32 dst = ( (*destPtr) >> 2 ) & 0x3f3f3f3f; |
|
*destPtr = src + src + src + dst; |
|
break; |
|
} |
|
case 4: |
|
*destPtr = fRenderInfo.fColor; |
|
} |
|
|
|
destPtr++; |
|
} |
|
src++; |
|
} |
|
destBasePtr = (UInt32 *)( (UInt8 *)destBasePtr + fRenderInfo.fDestStride ); |
|
src += fRenderInfo.fNumCols; |
|
} |
|
} |
|
|
|
void plFont::IRenderChar8To32( const plFont::plCharacter &c ) |
|
{ |
|
UInt8 *src = fBMapData + c.fBitmapOff; |
|
UInt32 *destPtr, *destBasePtr = (UInt32 *)( fRenderInfo.fDestPtr - c.fBaseline * fRenderInfo.fDestStride ); |
|
UInt16 x, y; |
|
UInt32 srcAlpha, oneMinusAlpha, r, g, b, dR, dG, dB, destAlpha, thisWidth; |
|
UInt8 srcR, srcG, srcB; |
|
|
|
|
|
// Unfortunately for some fonts, their right kern value actually is |
|
// farther left than the right edge of the bitmap (think of overlapping |
|
// script fonts). Ideally, we should store the actual width of each char's |
|
// bitmap and use that here. However, it really shouldn't make too big of a |
|
// difference, especially since the dest pixels that we end up overlapping |
|
// should already be in the cache. If it does, time to upgrade the font |
|
// format (again) |
|
thisWidth = fWidth;// + (Int32)c.fRightKern; |
|
|
|
if( (Int32)c.fHeight - (Int32)c.fBaseline >= fRenderInfo.fMaxHeight || thisWidth >= fRenderInfo.fMaxWidth || c.fBaseline > fRenderInfo.fY ) |
|
return; |
|
|
|
srcR = (UInt8)(( fRenderInfo.fColor >> 16 ) & 0x000000ff); |
|
srcG = (UInt8)(( fRenderInfo.fColor >> 8 ) & 0x000000ff); |
|
srcB = (UInt8)(( fRenderInfo.fColor ) & 0x000000ff); |
|
|
|
for( y = 0; y < c.fHeight; y++ ) |
|
{ |
|
destPtr = destBasePtr; |
|
for( x = 0; x < thisWidth; x++ ) |
|
{ |
|
if( src[ x ] == 255 ) |
|
destPtr[ x ] = fRenderInfo.fColor; |
|
else if( src[ x ] == 0 ) |
|
; // Empty |
|
else |
|
{ |
|
srcAlpha = ( src[ x ] * ( fRenderInfo.fColor >> 24 ) ) / 255; |
|
oneMinusAlpha = 255 - srcAlpha; |
|
|
|
destAlpha = destPtr[ x ] & 0xff000000; |
|
|
|
dR = ( destPtr[ x ] >> 16 ) & 0x000000ff; |
|
dG = ( destPtr[ x ] >> 8 ) & 0x000000ff; |
|
dB = ( destPtr[ x ] ) & 0x000000ff; |
|
r = ( srcR * srcAlpha ) >> 8; |
|
g = ( srcG * srcAlpha ) >> 8; |
|
b = ( srcB * srcAlpha ) >> 8; |
|
dR = ( dR * oneMinusAlpha ) >> 8; |
|
dG = ( dG * oneMinusAlpha ) >> 8; |
|
dB = ( dB * oneMinusAlpha ) >> 8; |
|
|
|
destPtr[ x ] = ( ( r + dR ) << 16 ) | ( ( g + dG ) << 8 ) | ( b + dB ) | destAlpha; |
|
} |
|
} |
|
destBasePtr = (UInt32 *)( (UInt8 *)destBasePtr + fRenderInfo.fDestStride ); |
|
src += fWidth; |
|
} |
|
} |
|
|
|
void plFont::IRenderChar8To32FullAlpha( const plFont::plCharacter &c ) |
|
{ |
|
UInt8 *src = fBMapData + c.fBitmapOff; |
|
UInt32 *destPtr, *destBasePtr = (UInt32 *)( fRenderInfo.fDestPtr - c.fBaseline * fRenderInfo.fDestStride ); |
|
UInt16 x, y; |
|
UInt32 destColorOnly, thisWidth; |
|
|
|
|
|
// Unfortunately for some fonts, their right kern value actually is |
|
// farther left than the right edge of the bitmap (think of overlapping |
|
// script fonts). Ideally, we should store the actual width of each char's |
|
// bitmap and use that here. However, it really shouldn't make too big of a |
|
// difference, especially since the dest pixels that we end up overlapping |
|
// should already be in the cache. If it does, time to upgrade the font |
|
// format (again) |
|
thisWidth = fWidth;// + (Int32)c.fRightKern; |
|
|
|
if( (Int32)c.fHeight - (Int32)c.fBaseline >= fRenderInfo.fMaxHeight || thisWidth >= fRenderInfo.fMaxWidth || c.fBaseline > fRenderInfo.fY ) |
|
return; |
|
|
|
destColorOnly = fRenderInfo.fColor & 0x00ffffff; |
|
|
|
for( y = 0; y < c.fHeight; y++ ) |
|
{ |
|
destPtr = destBasePtr; |
|
for( x = 0; x < thisWidth; x++ ) |
|
{ |
|
if( src[ x ] != 0 ) |
|
destPtr[ x ] = ( src[ x ] << 24 ) | destColorOnly; |
|
} |
|
destBasePtr = (UInt32 *)( (UInt8 *)destBasePtr + fRenderInfo.fDestStride ); |
|
src += fWidth; |
|
} |
|
} |
|
|
|
void plFont::IRenderChar8To32Alpha( const plFont::plCharacter &c ) |
|
{ |
|
UInt8 val, *src = fBMapData + c.fBitmapOff; |
|
UInt32 *destPtr, *destBasePtr = (UInt32 *)( fRenderInfo.fDestPtr - c.fBaseline * fRenderInfo.fDestStride ); |
|
UInt16 x, y; |
|
UInt32 destColorOnly, alphaMult, fullAlpha, thisWidth; |
|
|
|
|
|
// Unfortunately for some fonts, their right kern value actually is |
|
// farther left than the right edge of the bitmap (think of overlapping |
|
// script fonts). Ideally, we should store the actual width of each char's |
|
// bitmap and use that here. However, it really shouldn't make too big of a |
|
// difference, especially since the dest pixels that we end up overlapping |
|
// should already be in the cache. If it does, time to upgrade the font |
|
// format (again) |
|
thisWidth = fWidth;// + (Int32)c.fRightKern; |
|
|
|
if( (Int32)c.fHeight - (Int32)c.fBaseline >= fRenderInfo.fMaxHeight || thisWidth >= fRenderInfo.fMaxWidth || c.fBaseline > fRenderInfo.fY ) |
|
return; |
|
|
|
destColorOnly = fRenderInfo.fColor & 0x00ffffff; |
|
// alphaMult should come out to be a value to satisfy (fontAlpha * alphaMult >> 8) as the right alpha, |
|
// but then we want it so (fontAlpha * alphaMult) will be in the upper 8 bits |
|
fullAlpha = fRenderInfo.fColor & 0xff000000; |
|
alphaMult = fullAlpha / 255; |
|
|
|
for( y = 0; y < c.fHeight; y++ ) |
|
{ |
|
destPtr = destBasePtr; |
|
for( x = 0; x < thisWidth; x++ ) |
|
{ |
|
val = src[ x ]; |
|
if( val == 0xff ) |
|
destPtr[ x ] = fullAlpha | destColorOnly; |
|
else if( val != 0 ) |
|
{ |
|
destPtr[ x ] = ( ( alphaMult * val ) & 0xff000000 ) | destColorOnly; |
|
} |
|
} |
|
destBasePtr = (UInt32 *)( (UInt8 *)destBasePtr + fRenderInfo.fDestStride ); |
|
src += fWidth; |
|
} |
|
} |
|
|
|
|
|
void plFont::IRenderCharNull( const plCharacter &c ) |
|
{ |
|
} |
|
|
|
//// CalcString Variations //////////////////////////////////////////////////// |
|
|
|
UInt16 plFont::CalcStringWidth( const char *string ) |
|
{ |
|
UInt16 w, h, a, lX, lY; |
|
UInt32 s; |
|
CalcStringExtents( string, w, h, a, s, lX, lY ); |
|
return w; |
|
} |
|
|
|
UInt16 plFont::CalcStringWidth( const wchar_t *string ) |
|
{ |
|
UInt16 w, h, a, lX, lY; |
|
UInt32 s; |
|
CalcStringExtents( string, w, h, a, s, lX, lY ); |
|
return w; |
|
} |
|
|
|
void plFont::CalcStringExtents( const char *string, UInt16 &width, UInt16 &height, UInt16 &ascent, UInt32 &firstClippedChar, UInt16 &lastX, UInt16 &lastY ) |
|
{ |
|
// convert the char string to a wchar_t string |
|
wchar_t *wideString = hsStringToWString(string); |
|
CalcStringExtents(wideString,width,height,ascent,firstClippedChar,lastX,lastY); |
|
delete [] wideString; |
|
} |
|
|
|
void plFont::CalcStringExtents( const wchar_t *string, UInt16 &width, UInt16 &height, UInt16 &ascent, UInt32 &firstClippedChar, UInt16 &lastX, UInt16 &lastY ) |
|
{ |
|
IRenderString( nil, 0, 0, string, true ); |
|
width = fRenderInfo.fFarthestX; |
|
height = (UInt16)(fRenderInfo.fY + fFontDescent);//fRenderInfo.fMaxDescent; |
|
ascent = fRenderInfo.fMaxAscent; |
|
lastX = fRenderInfo.fLastX; |
|
lastY = fRenderInfo.fLastY; |
|
|
|
// firstClippedChar is an index into the given string that points to the start of the part of the string |
|
// that got clipped (i.e. not rendered). |
|
firstClippedChar = (UInt32)fRenderInfo.fVolatileStringPtr - (UInt32)string; |
|
firstClippedChar /= 2; // divide by 2 because a wchar_t is two bytes wide, instead of one (like a char) |
|
} |
|
|
|
//// IGetFreeCharData ///////////////////////////////////////////////////////// |
|
// Used for constructing fonts one character at a time; finds a pointer to |
|
// the first free space in our allocated font bitmap, based on the heights |
|
// and offsets of all the current characters |
|
|
|
UInt8 *plFont::IGetFreeCharData( UInt32 &newOffset ) |
|
{ |
|
Int32 i; |
|
|
|
|
|
newOffset = 0; |
|
for( i = fCharacters.GetCount() - 1; i >= 0; i-- ) |
|
{ |
|
UInt32 thisOff = fCharacters[ i ].fBitmapOff + ( ( fCharacters[ i ].fHeight * fWidth * fBPP ) >> 3 ); |
|
if( newOffset < thisOff ) |
|
{ |
|
newOffset = thisOff; |
|
break; |
|
} |
|
} |
|
|
|
if( newOffset >= ( ( fWidth * fHeight * fBPP ) >> 3 ) ) |
|
{ |
|
hsAssert( false, "Invalid position found in IGetFreeCharData()" ); |
|
return nil; |
|
} |
|
|
|
// Return pointer to the new area |
|
return fBMapData + newOffset; |
|
} |
|
|
|
//// LoadFromP2FFile ////////////////////////////////////////////////////////// |
|
// Handy quick wrapper |
|
|
|
hsBool plFont::LoadFromP2FFile( const char *path ) |
|
{ |
|
hsUNIXStream stream; |
|
if( stream.Open( path, "rb" ) ) |
|
{ |
|
ReadRaw( &stream ); |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
//// LoadFromFNT ////////////////////////////////////////////////////////////// |
|
// Load this font from the data found in the given Windows FNT file, |
|
// using the format specified in the Windows 3 Developers Notes. |
|
|
|
hsBool plFont::LoadFromFNT( const char *path ) |
|
{ |
|
hsUNIXStream stream; // Ahh, irony |
|
if( !stream.Open( path, "rb" ) ) |
|
return false; |
|
|
|
return LoadFromFNTStream( &stream ); |
|
} |
|
|
|
hsBool plFont::LoadFromFNTStream( hsStream *stream ) |
|
{ |
|
IClear(); |
|
|
|
try |
|
{ |
|
|
|
// Note: hsUNIXStreams just happen to store in the same endian as Windows... |
|
struct FNTInfo |
|
{ |
|
UInt16 version; |
|
UInt32 size; |
|
char copyright[ 60 ]; |
|
UInt16 type; |
|
UInt16 points; |
|
UInt16 vertRes; |
|
UInt16 horzRes; |
|
UInt16 ascent; |
|
UInt16 internalLeading; |
|
UInt16 externalLeading; |
|
UInt8 italic, underline, strikeout; |
|
UInt16 weight; |
|
UInt8 charSet; |
|
UInt16 pixWidth; // 0 means variable width chars |
|
UInt16 pixHeight; |
|
UInt8 pitchFamily; |
|
UInt16 avgWidth; |
|
UInt16 maxWidth; |
|
UInt8 firstChar, lastChar, defaultChar, breakChar; |
|
UInt16 widthBytes; |
|
UInt32 device, face; |
|
UInt32 bitsPointer, bitsOffset; |
|
UInt8 reserved; |
|
UInt32 flags; |
|
UInt16 aSpace, bSpace, cSpace; |
|
UInt32 colorPointer; |
|
UInt8 reserved1[ 16 ]; |
|
|
|
void Read( hsStream *s ) |
|
{ |
|
version = s->ReadSwap16(); |
|
size = s->ReadSwap32(); |
|
|
|
s->Read( sizeof( copyright ), copyright ); |
|
|
|
s->ReadSwap( &type ); |
|
s->ReadSwap( &points ); |
|
s->ReadSwap( &vertRes ); |
|
s->ReadSwap( &horzRes ); |
|
s->ReadSwap( &ascent ); |
|
s->ReadSwap( &internalLeading ); |
|
s->ReadSwap( &externalLeading ); |
|
s->ReadSwap( &italic ); |
|
s->ReadSwap( &underline ); |
|
s->ReadSwap( &strikeout ); |
|
s->ReadSwap( &weight ); |
|
s->ReadSwap( &charSet ); |
|
s->ReadSwap( &pixWidth ); |
|
s->ReadSwap( &pixHeight ); |
|
s->ReadSwap( &pitchFamily ); |
|
s->ReadSwap( &avgWidth ); |
|
s->ReadSwap( &maxWidth ); |
|
s->ReadSwap( &firstChar ); |
|
s->ReadSwap( &lastChar ); |
|
s->ReadSwap( &defaultChar ); |
|
s->ReadSwap( &breakChar ); |
|
s->ReadSwap( &widthBytes ); |
|
s->ReadSwap( &device ); |
|
s->ReadSwap( &face ); |
|
s->ReadSwap( &bitsPointer ); |
|
s->ReadSwap( &bitsOffset ); |
|
s->ReadSwap( &reserved ); |
|
if( version == 0x0300 ) |
|
{ |
|
s->ReadSwap( &flags ); |
|
s->ReadSwap( &aSpace ); |
|
s->ReadSwap( &bSpace ); |
|
s->ReadSwap( &cSpace ); |
|
s->ReadSwap( &colorPointer ); |
|
s->Read( sizeof( reserved1 ), reserved1 ); |
|
} |
|
else |
|
{ |
|
flags = 0; |
|
aSpace = bSpace = cSpace = 0; |
|
colorPointer = 0; |
|
} |
|
} |
|
} fntInfo; |
|
|
|
fntInfo.Read( stream ); |
|
|
|
struct charEntry |
|
{ |
|
UInt16 width; |
|
UInt32 offset; |
|
charEntry() { width = 0; offset = 0; } |
|
} *charEntries; |
|
|
|
int i, count = fntInfo.lastChar - fntInfo.firstChar + 2; |
|
charEntries = TRACKED_NEW charEntry[ count ]; |
|
for( i = 0; i < count; i++ ) |
|
{ |
|
charEntries[ i ].width = stream->ReadSwap16(); |
|
if( fntInfo.version == 0x0200 ) |
|
charEntries[ i ].offset = stream->ReadSwap16(); |
|
else |
|
charEntries[ i ].offset = stream->ReadSwap32(); |
|
} |
|
|
|
char faceName[ 256 ], deviceName[ 256 ]; |
|
if( fntInfo.face != 0 ) |
|
{ |
|
stream->SetPosition( fntInfo.face ); |
|
for( i = 0; i < 256; i++ ) |
|
{ |
|
faceName[ i ] = stream->ReadByte(); |
|
if( faceName[ i ] == 0 ) |
|
break; |
|
} |
|
strncpy( fFace, faceName, sizeof( fFace ) ); |
|
} |
|
if( fntInfo.device != 0 ) |
|
{ |
|
stream->SetPosition( fntInfo.device ); |
|
for( i = 0; i < 256; i++ ) |
|
{ |
|
deviceName[ i ] = stream->ReadByte(); |
|
if( deviceName[ i ] == 0 ) |
|
break; |
|
} |
|
} |
|
fSize = (UInt8)(fntInfo.points); |
|
|
|
// Figure out what we need to allocate our bitmap as |
|
fWidth = 0; |
|
fHeight = 0; |
|
for( i = 0; i < count; i++ ) |
|
{ |
|
if( fWidth < charEntries[ i ].width ) |
|
fWidth = charEntries[ i ].width; |
|
if( charEntries[ i ].offset > 0 ) |
|
fHeight += fntInfo.pixHeight; |
|
} |
|
fBPP = 1; |
|
|
|
// Since we're 1 bbp, make sure width is a multiple of 8 |
|
fWidth = ( ( fWidth + 7 ) >> 3 ) << 3; |
|
|
|
UInt32 widthInBytes = ( fWidth * fBPP ) >> 3; |
|
|
|
// Allocate our bitmap now |
|
UInt32 size = widthInBytes * fHeight; |
|
fBMapData = TRACKED_NEW UInt8[ size ]; |
|
memset( fBMapData, 0, size ); |
|
|
|
fFirstChar = fntInfo.firstChar; |
|
fMaxCharHeight = 0; |
|
|
|
// Read the bitmap info |
|
UInt32 destOff = 0; |
|
for( i = 0; i < count; i++ ) |
|
{ |
|
int numCols = ( charEntries[ i ].width + 7 ) >> 3; |
|
int height = fntInfo.pixHeight; |
|
|
|
// Write a record for this char |
|
plCharacter outChar; |
|
outChar.fBitmapOff = destOff; |
|
outChar.fHeight = ( charEntries[ i ].offset == 0 ) ? 0 : height; |
|
outChar.fBaseline = fntInfo.ascent; |
|
outChar.fLeftKern = 0.f; |
|
outChar.fRightKern = (hsScalar)(charEntries[ i ].width - fWidth); |
|
fCharacters.Append( outChar ); |
|
|
|
if( outChar.fHeight > fMaxCharHeight ) |
|
fMaxCharHeight = outChar.fHeight; |
|
|
|
if( charEntries[ i ].offset == 0 ) |
|
continue; |
|
|
|
// Seek to this char |
|
stream->SetPosition( charEntries[ i ].offset ); |
|
|
|
// Write the actual bitmap data. Note: FNTs store the bits one column at a time, |
|
// one col after another, whereas we want it plain row by row |
|
int col, y; |
|
UInt8 *basePtr = fBMapData + destOff; |
|
for( col = 0; col < numCols; col++ ) |
|
{ |
|
UInt8 *yPtr = basePtr; |
|
for( y = 0; y < height; y++ ) |
|
{ |
|
*yPtr = stream->ReadByte(); |
|
yPtr += widthInBytes; |
|
} |
|
basePtr++; |
|
} |
|
|
|
destOff += widthInBytes * outChar.fHeight; |
|
} |
|
|
|
delete [] charEntries; |
|
|
|
ICalcFontAscent(); |
|
return true; |
|
} |
|
catch( ... ) |
|
{ |
|
// Somehow we crashed converting! |
|
IClear(); |
|
return false; |
|
} |
|
} |
|
|
|
//// LoadFromBDF ////////////////////////////////////////////////////////////// |
|
// Load this font from the data found in the given Adobe Systems BDF file, |
|
// using the format specified in the Glyph Bitmap Distribution Format (BDF) |
|
// Specification Version 2.2 from Adobe Systems. |
|
|
|
/*hsBool plFont::LoadFromBDF( const char *path, plBDFConvertCallback *callback ) |
|
{ |
|
hsUNIXStream stream; // Ahh, irony |
|
if( !stream.Open( path, "rb" ) ) |
|
return false; |
|
|
|
return LoadFromBDFStream( &stream, callback ); |
|
} |
|
*/ |
|
// Some parsing helpers |
|
typedef int (*fnCharTester)( int ); |
|
|
|
static int sQuoteTester( int c ) |
|
{ |
|
return ( c == '\"' ); |
|
} |
|
|
|
static int sDashTester( int c ) |
|
{ |
|
return isspace( c ) || ( c == '-' ); |
|
} |
|
|
|
class plLineParser |
|
{ |
|
protected: |
|
static char fLine[ 512 ]; |
|
|
|
char *fCursor, fRestore; |
|
|
|
void IAdvanceToNextToken( fnCharTester tester = isspace ) |
|
{ |
|
// Last cursor |
|
*fCursor = fRestore; |
|
|
|
// Scan for the start of the next token |
|
while( *fCursor != 0 && (*tester)( *fCursor ) ) |
|
fCursor++; |
|
|
|
if( *fCursor == 0 ) |
|
return; |
|
|
|
// This is the start of our token |
|
const char *start = fCursor; |
|
|
|
// Put a stopper here |
|
fRestore = *fCursor; |
|
*fCursor = 0; |
|
|
|
// And return! |
|
return; |
|
} |
|
|
|
const char *IGetNextToken( fnCharTester tester = isspace ) |
|
{ |
|
// Last cursor |
|
*fCursor = fRestore; |
|
|
|
// Scan for the start of the next token |
|
while( *fCursor != 0 && (*tester)( *fCursor ) ) |
|
fCursor++; |
|
|
|
if( *fCursor == 0 ) |
|
return nil; |
|
|
|
// This is the start of our token; find the end |
|
const char *start = fCursor; |
|
while( *fCursor != 0 && !(*tester)( *fCursor ) ) |
|
fCursor++; |
|
|
|
// Put a stopper here |
|
fRestore = *fCursor; |
|
*fCursor = 0; |
|
|
|
// And return! |
|
return start; |
|
} |
|
|
|
public: |
|
|
|
plLineParser( const char *line ) |
|
{ |
|
strncpy( fLine, line, sizeof( fLine ) ); |
|
fCursor = fLine; |
|
fRestore = *fCursor; |
|
} |
|
|
|
~plLineParser() { } |
|
|
|
const char *GetKeyword( void ) |
|
{ |
|
return IGetNextToken(); |
|
} |
|
|
|
const char *GetString( void ) |
|
{ |
|
IAdvanceToNextToken(); |
|
return IGetNextToken( sQuoteTester ); |
|
} |
|
|
|
const char *GetKeywordNoDashes( void ) |
|
{ |
|
return IGetNextToken( sDashTester ); |
|
} |
|
|
|
Int32 GetInt( void ) |
|
{ |
|
return atoi( IGetNextToken() ); |
|
} |
|
|
|
hsScalar GetFloat( void ) |
|
{ |
|
return (hsScalar)atof( IGetNextToken() ); |
|
} |
|
}; |
|
|
|
char plLineParser::fLine[ 512 ]; |
|
|
|
|
|
// Another helper--parser for various sections of the BDF format |
|
class plBDFSectParser |
|
{ |
|
protected: |
|
plFont &fFont; |
|
plBDFConvertCallback *fCallback; |
|
|
|
public: |
|
plBDFSectParser( plFont &myFont, plBDFConvertCallback *callback ) : fFont( myFont ), fCallback( callback ) {} |
|
|
|
virtual plBDFSectParser *ParseKeyword( const char *keyword, plLineParser &line ) |
|
{ |
|
return nil; |
|
} |
|
}; |
|
|
|
|
|
class plBDFLookForEndCharParser : public plBDFSectParser |
|
{ |
|
public: |
|
plBDFLookForEndCharParser( plFont &myFont, plBDFConvertCallback *callback ) : plBDFSectParser( myFont, callback ) {} |
|
|
|
virtual plBDFSectParser *ParseKeyword( const char *keyword, plLineParser &line ); |
|
}; |
|
|
|
inline UInt8 iHexCharToByte( char c ) |
|
{ |
|
switch( c ) |
|
{ |
|
case '0': return 0; |
|
case '1': return 1; |
|
case '2': return 2; |
|
case '3': return 3; |
|
case '4': return 4; |
|
case '5': return 5; |
|
case '6': return 6; |
|
case '7': return 7; |
|
case '8': return 8; |
|
case '9': return 9; |
|
case 'a': case 'A': return 10; |
|
case 'b': case 'B': return 11; |
|
case 'c': case 'C': return 12; |
|
case 'd': case 'D': return 13; |
|
case 'e': case 'E': return 14; |
|
case 'f': case 'F': return 15; |
|
default: return 0; |
|
} |
|
} |
|
inline UInt8 iHexStringToByte( const char *str ) |
|
{ |
|
return ( iHexCharToByte( str[ 0 ] ) << 4 ) | iHexCharToByte( str[ 1 ] ); |
|
} |
|
|
|
class plBDFCharsParser : public plBDFSectParser |
|
{ |
|
protected: |
|
|
|
// Info about the current character we're translating |
|
UInt16 fWhichChar, fRowsLeft; |
|
plFont::plCharacter *fCharacter; |
|
UInt8 *fBitmap; |
|
hsBool fDoingData; |
|
UInt32 fBytesWide, fBMapStride; |
|
|
|
inline void IReset( void ) |
|
{ |
|
fBitmap = nil; |
|
fCharacter = nil; |
|
fWhichChar = 0; |
|
fDoingData = false; |
|
} |
|
|
|
public: |
|
static UInt32 fResolution; |
|
|
|
plBDFCharsParser( plFont &myFont, plBDFConvertCallback *callback ) : plBDFSectParser( myFont, callback ), fDoingData( false ) {} |
|
|
|
virtual plBDFSectParser *ParseKeyword( const char *keyword, plLineParser &line ) |
|
{ |
|
if( strcmp( keyword, "ENDFONT" ) == 0 ) |
|
{ |
|
// All done! |
|
return nil; |
|
} |
|
else if( strcmp( keyword, "ENDCHAR" ) == 0 ) |
|
{ |
|
// End of the character, reset |
|
IReset(); |
|
if( fCallback != nil ) |
|
fCallback->CharDone(); |
|
} |
|
else if( fDoingData ) |
|
{ |
|
// If we're doing data, all lines are hex values until we hit "ENDCHAR" |
|
if( fRowsLeft == 0 ) |
|
throw; |
|
|
|
int hDigit; |
|
for( hDigit = 0; *keyword != 0 && hDigit < fBytesWide; hDigit++, keyword += 2 ) |
|
{ |
|
*fBitmap = iHexStringToByte( keyword ); |
|
fBitmap++; |
|
} |
|
|
|
fBitmap += fBMapStride - hDigit; |
|
fRowsLeft--; |
|
} |
|
else if( strcmp( keyword, "STARTCHAR" ) == 0 ) |
|
{ |
|
// Start of a new character--ignore the name, we'll just use the encoding |
|
IReset(); |
|
} |
|
else if( strcmp( keyword, "ENCODING" ) == 0 ) |
|
{ |
|
int ch = line.GetInt(); |
|
if( ch == -1 ) |
|
{ |
|
// Nonstandard encoding, skip the character entirely |
|
return TRACKED_NEW plBDFLookForEndCharParser( fFont, fCallback ); |
|
} |
|
else |
|
{ |
|
fWhichChar = (UInt16)(UInt8)ch; |
|
|
|
// Set up our pointer. Note that since BDFs don't tell us the starting character, |
|
// we just make it 0 |
|
if( fFont.fCharacters.GetCount() < fWhichChar + 1 ) |
|
fFont.fCharacters.SetCount( fWhichChar + 1 ); |
|
|
|
fCharacter = &fFont.fCharacters[ fWhichChar ]; |
|
} |
|
} |
|
// Horizontal mode offsets (writing mode 0) |
|
else if( strcmp( keyword, "SWIDTH" ) == 0 ) |
|
{ |
|
// In device units, unused |
|
// float fWidth = (float)line.GetInt() * ( (float)fFont.GetSize() / 1000.f ) * ( fResolution / 72.f ); |
|
// fCharacter->fRightKern = fWidth - (float)fFont.fWidth; |
|
// fCharacter->fLeftKern = 0.f; |
|
} |
|
else if( strcmp( keyword, "DWIDTH" ) == 0 ) |
|
{ |
|
// In pixels, basically the offset to the next char. Note that we only |
|
// support the X direction (altho admittedly the idea of a font whose characters cause |
|
// the next chars to go up or down is way nifty) |
|
fCharacter->fRightKern = (float)line.GetInt() - (float)fFont.fWidth; |
|
fCharacter->fLeftKern = 0.f; |
|
} |
|
// Vertical mode offsets (unsupported) |
|
else if( strcmp( keyword, "SWIDTH1" ) == 0 ) |
|
{ |
|
|
|
} |
|
else if( strcmp( keyword, "DWIDTH1" ) == 0 ) |
|
{ |
|
|
|
} |
|
// Bitmap bounding box |
|
else if( strcmp( keyword, "BBX" ) == 0 ) |
|
{ |
|
int pixW = line.GetInt(); |
|
int pixH = line.GetInt(); |
|
int xOff = line.GetInt(); |
|
int yOff = line.GetInt(); |
|
|
|
// Got enough info now to allocate us a bitmap for this char |
|
fBitmap = fFont.IGetFreeCharData( fCharacter->fBitmapOff ); |
|
if( fBitmap == nil ) |
|
throw false; |
|
|
|
if( fCharacter->fBitmapOff > ( ( fFont.fWidth * ( fFont.fHeight - pixH ) * fFont.fBPP ) >> 3 ) ) |
|
{ |
|
hsAssert( false, "Invalid position found in IGetFreeCharData()" ); |
|
return nil; |
|
} |
|
|
|
// Set these now, since setting them before would've changed the IGetFreeCharData results |
|
fCharacter->fLeftKern = (hsScalar)xOff; |
|
fCharacter->fBaseline = yOff + pixH; |
|
fCharacter->fHeight = fRowsLeft = pixH; |
|
|
|
fBytesWide = ( pixW + 7 ) >> 3; |
|
fBMapStride = fFont.fWidth >> 3; |
|
} |
|
// Bitmap data |
|
else if( strcmp( keyword, "BITMAP" ) == 0 ) |
|
{ |
|
fDoingData = true; |
|
} |
|
// This keyword is outputted from the "getbdf" utility on linux, for some reason... |
|
else if( strcmp( keyword, "ATTRIBUTES" ) == 0 ) |
|
{ |
|
|
|
} |
|
else |
|
throw false; // Invalid keyword |
|
|
|
return this; |
|
} |
|
}; |
|
|
|
UInt32 plBDFCharsParser::fResolution = 72; |
|
|
|
|
|
plBDFSectParser *plBDFLookForEndCharParser::ParseKeyword( const char *keyword, plLineParser &line ) |
|
{ |
|
if( strcmp( keyword, "ENDCHAR" ) == 0 ) |
|
{ |
|
// Horray! |
|
return TRACKED_NEW plBDFCharsParser( fFont, fCallback ); |
|
} |
|
// Just gobble lines until we find the endchar section |
|
return this; |
|
} |
|
|
|
class plBDFLookForCharParser : public plBDFSectParser |
|
{ |
|
public: |
|
plBDFLookForCharParser( plFont &myFont, plBDFConvertCallback *callback ) : plBDFSectParser( myFont, callback ) {} |
|
|
|
virtual plBDFSectParser *ParseKeyword( const char *keyword, plLineParser &line ) |
|
{ |
|
if( strcmp( keyword, "CHARS" ) == 0 ) |
|
{ |
|
// Horray! |
|
return TRACKED_NEW plBDFCharsParser( fFont, fCallback ); |
|
} |
|
// Just gobble lines until we find the chars section |
|
return this; |
|
} |
|
}; |
|
|
|
class plBDFPropertiesParser : public plBDFSectParser |
|
{ |
|
public: |
|
plBDFPropertiesParser( plFont &myFont, plBDFConvertCallback *callback ) : plBDFSectParser( myFont, callback ) {} |
|
|
|
virtual plBDFSectParser *ParseKeyword( const char *keyword, plLineParser &line ) |
|
{ |
|
// Note: the properties section is entirely optional and arbitrary, but we |
|
// parse it in case we can get more accurate info about the font name and props |
|
if( strcmp( keyword, "FACE_NAME" ) == 0 ) |
|
{ |
|
fFont.SetFace( line.GetString() ); |
|
} |
|
else if( strcmp( keyword, "WEIGHT_NAME" ) == 0 ) |
|
{ |
|
if( stricmp( line.GetString(), "Bold" ) == 0 ) |
|
fFont.SetFlag( plFont::kFlagBold, true ); |
|
} |
|
else if( strcmp( keyword, "ENDPROPERTIES" ) == 0 ) |
|
{ |
|
// Switch to waiting for the chars section |
|
return TRACKED_NEW plBDFLookForCharParser( fFont, fCallback ); |
|
} |
|
// All tokens are technically valid, even if we don't recognize them |
|
return this; |
|
} |
|
}; |
|
|
|
class plBDFHeaderParser : public plBDFSectParser |
|
{ |
|
public: |
|
plBDFHeaderParser( plFont &myFont, plBDFConvertCallback *callback ) : plBDFSectParser( myFont, callback ) {} |
|
|
|
virtual plBDFSectParser *ParseKeyword( const char *keyword, plLineParser &line ) |
|
{ |
|
if( strcmp( keyword, "STARTFONT" ) == 0 ) |
|
{ |
|
// Start of the font; check version # |
|
float version = line.GetFloat(); |
|
if( version < 2.1f ) |
|
throw false; |
|
|
|
// Initial font values |
|
fFont.fFirstChar = 0; |
|
fFont.fMaxCharHeight = 0; |
|
} |
|
else if( strcmp( keyword, "FONT" ) == 0 ) |
|
{ |
|
// Postscript-style font name: figure out our face name and hopefully attributes too |
|
// Format is usually of the form: vendor-face-weight-otherStuff |
|
const char *vendor = line.GetKeywordNoDashes(); |
|
const char *face = line.GetKeywordNoDashes(); |
|
|
|
fFont.SetFace( face != nil ? face : vendor ); |
|
|
|
const char *weight = line.GetKeywordNoDashes(); |
|
|
|
if( weight != nil && stricmp( weight, "Bold" ) == 0 ) |
|
fFont.SetFlag( plFont::kFlagBold, true ); |
|
} |
|
else if( strcmp( keyword, "COMMENT" ) == 0 ) |
|
{ |
|
// Comment, ignore |
|
} |
|
else if( strcmp( keyword, "CONTENTVERSION" ) == 0 ) |
|
{ |
|
// Content version, app specific, not used by us |
|
} |
|
else if( strcmp( keyword, "SIZE" ) == 0 ) |
|
{ |
|
// Point size and target resolution of this font |
|
fFont.SetSize( (UInt8)(line.GetInt()) ); |
|
plBDFCharsParser::fResolution = line.GetInt(); |
|
} |
|
else if( strcmp( keyword, "FONTBOUNDINGBOX" ) == 0 ) |
|
{ |
|
// Max dimensions and base offsets |
|
// Note that we should've already grabbed the width from our |
|
// prescan earlier, to guard against malformed BDFs |
|
int thisWidth = line.GetInt(); |
|
if( fFont.fWidth < thisWidth ) |
|
fFont.fWidth = thisWidth; |
|
|
|
// Since we're 1 bbp, make sure width is a multiple of 8 |
|
fFont.fWidth = ( ( fFont.fWidth + 7 ) >> 3 ) << 3; |
|
|
|
// Allocate our data now |
|
fFont.fBMapData = TRACKED_NEW UInt8[ ( fFont.fWidth * fFont.fHeight * fFont.fBPP ) >> 3 ]; |
|
memset( fFont.fBMapData, 0, ( fFont.fWidth * fFont.fHeight * fFont.fBPP ) >> 3 ); |
|
|
|
fFont.fMaxCharHeight = line.GetInt(); |
|
} |
|
else if( strcmp( keyword, "METRICSSET" ) == 0 ) |
|
{ |
|
// Metrics set specification; unsupported |
|
} |
|
else if( strcmp( keyword, "STARTPROPERTIES" ) == 0 ) |
|
{ |
|
// Start of the properties section, switch to that |
|
return TRACKED_NEW plBDFPropertiesParser( fFont, fCallback ); |
|
} |
|
else if( strcmp( keyword, "CHARS" ) == 0 ) |
|
{ |
|
// Allocate our bitmap if we haven't already |
|
|
|
// Since we're 1 bbp, make sure width is a multiple of 8 |
|
if( fFont.fBMapData == nil ) |
|
{ |
|
fFont.fWidth = ( ( fFont.fWidth + 7 ) >> 3 ) << 3; |
|
|
|
// Allocate our data now |
|
fFont.fBMapData = TRACKED_NEW UInt8[ ( fFont.fWidth * fFont.fHeight * fFont.fBPP ) >> 3 ]; |
|
memset( fFont.fBMapData, 0, ( fFont.fWidth * fFont.fHeight * fFont.fBPP ) >> 3 ); |
|
} |
|
|
|
// Start of the char section |
|
return TRACKED_NEW plBDFCharsParser( fFont, fCallback ); |
|
} |
|
else |
|
{ |
|
// Unrecognized token, abort |
|
throw false; |
|
} |
|
|
|
// Default, keep going with our parser |
|
return this; |
|
} |
|
}; |
|
|
|
class plBDFCheckDimsParser : public plBDFSectParser |
|
{ |
|
hsBool fSkipNext; |
|
|
|
public: |
|
|
|
UInt32 fMaxWidth, fMaxHeight, fNumChars, fTotalHeight; |
|
UInt16 fMaxChar; |
|
|
|
plBDFCheckDimsParser( plFont &myFont ) : plBDFSectParser( myFont, nil ) { fMaxWidth = fMaxHeight = fNumChars = fTotalHeight = 0; fSkipNext = false; fMaxChar = 0; } |
|
|
|
virtual plBDFSectParser *ParseKeyword( const char *keyword, plLineParser &line ) |
|
{ |
|
if( strcmp( keyword, "ENDFONT" ) == 0 ) |
|
{ |
|
// All done! |
|
return nil; |
|
} |
|
// Encoding |
|
else if( strcmp( keyword, "ENCODING" ) == 0 ) |
|
{ |
|
int ch = line.GetInt(); |
|
if( ch == -1 ) |
|
// We skip these |
|
fSkipNext = true; |
|
else |
|
{ |
|
fSkipNext = false; |
|
if( fMaxChar < ch ) |
|
fMaxChar = ch; |
|
} |
|
} |
|
// Bitmap bounding box |
|
else if( strcmp( keyword, "BBX" ) == 0 ) |
|
{ |
|
if( fSkipNext ) |
|
return this; |
|
|
|
int pixW = line.GetInt(); |
|
int pixH = line.GetInt(); |
|
int xOff = line.GetInt(); |
|
int yOff = line.GetInt(); |
|
|
|
if( fMaxWidth < pixW ) |
|
fMaxWidth = pixW; |
|
if( fMaxHeight < pixH ) |
|
fMaxHeight = pixH; |
|
|
|
fNumChars++; |
|
|
|
fTotalHeight += pixH; |
|
} |
|
return this; |
|
} |
|
}; |
|
|
|
hsBool plFont::LoadFromBDFStream( hsStream *stream, plBDFConvertCallback *callback ) |
|
{ |
|
return false; |
|
} |
|
|
|
hsBool plFont::LoadFromBDF( const char *path, plBDFConvertCallback *callback ) |
|
{ |
|
FILE *fp = fopen( path, "rt" ); |
|
if( fp == nil ) |
|
return false; |
|
try |
|
{ |
|
IClear(); |
|
|
|
char line[ 512 ]; |
|
|
|
// Run through the entire file first with a plBDFCheckDimsParser. This is because |
|
// some BDFs are naughty and don't report the correct fontboundingbox (i.e. too small) |
|
// (See the following loop below for details on the workings of this loop) |
|
plBDFCheckDimsParser checkDims( *this ); |
|
while( fgets( line, sizeof( line ), fp ) ) |
|
{ |
|
plLineParser parser( line ); |
|
const char *keyword = parser.GetKeyword(); |
|
if( keyword != nil ) |
|
{ |
|
if( checkDims.ParseKeyword( keyword, parser ) == nil ) |
|
break; |
|
} |
|
} |
|
|
|
// Set up initial values |
|
fWidth = checkDims.fMaxWidth; |
|
fHeight = checkDims.fTotalHeight; |
|
fBPP = 1; |
|
|
|
// Pre-expand our char list |
|
fCharacters.ExpandAndZero( checkDims.fMaxChar + 1 ); |
|
fCharacters.SetCount( 0 ); |
|
|
|
if( callback != nil ) |
|
callback->NumChars( (UInt16)(checkDims.fNumChars) ); |
|
|
|
// Rewind and continue normally |
|
fseek( fp, 0, SEEK_SET ); |
|
|
|
// Start with the header parser |
|
plBDFSectParser *currParser = TRACKED_NEW plBDFHeaderParser( *this, callback ); |
|
|
|
// Read from the stream one line at a time (don't want comments, and char #1 should be invalid for a BDF) |
|
while( fgets( line, sizeof( line ), fp ) && currParser != nil ) |
|
{ |
|
// Parse this one |
|
plLineParser parser( line ); |
|
|
|
// Get the keyword for this line |
|
const char *keyword = parser.GetKeyword(); |
|
if( keyword != nil ) |
|
{ |
|
// Pass on to the current parser |
|
plBDFSectParser *newParser = currParser->ParseKeyword( keyword, parser ); |
|
if( newParser != currParser ) |
|
{ |
|
delete currParser; |
|
currParser = newParser; |
|
} |
|
} |
|
} |
|
} |
|
catch( ... ) |
|
{ |
|
IClear(); |
|
return false; |
|
} |
|
|
|
fclose( fp ); |
|
|
|
ICalcFontAscent(); |
|
return true; |
|
} |
|
|
|
hsBool plFont::ReadRaw( hsStream *s ) |
|
{ |
|
s->Read( sizeof( fFace ), fFace ); |
|
fSize = s->ReadByte(); |
|
s->ReadSwap( &fFlags ); |
|
|
|
s->ReadSwap( &fWidth ); |
|
s->ReadSwap( &fHeight ); |
|
s->ReadSwap( &fMaxCharHeight ); |
|
|
|
fBPP = s->ReadByte(); |
|
|
|
UInt32 size = ( fWidth * fHeight * fBPP ) >> 3; |
|
if( size > 0 ) |
|
{ |
|
fBMapData = TRACKED_NEW UInt8[ size ]; |
|
s->Read( size, fBMapData ); |
|
} |
|
else |
|
fBMapData = nil; |
|
|
|
s->ReadSwap( &fFirstChar ); |
|
|
|
UInt32 i; |
|
fCharacters.SetCountAndZero( s->ReadSwap32() ); |
|
for( i = 0; i < fCharacters.GetCount(); i++ ) |
|
fCharacters[ i ].Read( s ); |
|
|
|
ICalcFontAscent(); |
|
|
|
return true; |
|
} |
|
|
|
hsBool plFont::WriteRaw( hsStream *s ) |
|
{ |
|
s->Write( sizeof( fFace ), fFace ); |
|
s->WriteByte( fSize ); |
|
s->WriteSwap( fFlags ); |
|
|
|
s->WriteSwap( fWidth ); |
|
s->WriteSwap( fHeight ); |
|
s->WriteSwap( fMaxCharHeight ); |
|
|
|
s->WriteByte( fBPP ); |
|
|
|
UInt32 size = ( fWidth * fHeight * fBPP ) >> 3; |
|
if( size > 0 ) |
|
s->Write( size, fBMapData ); |
|
|
|
s->WriteSwap( fFirstChar ); |
|
|
|
UInt32 i; |
|
s->WriteSwap32( fCharacters.GetCount() ); |
|
for( i = 0; i < fCharacters.GetCount(); i++ ) |
|
fCharacters[ i ].Write( s ); |
|
|
|
return true; |
|
}
|
|
|