/*==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 . 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==*/ /////////////////////////////////////////////////////////////////////////////// // // // plTextGenerator Class Functions // // Cyan, Inc. // // // //// Version History ////////////////////////////////////////////////////////// // // // 12.13.2001 mcn - Created. // // // /////////////////////////////////////////////////////////////////////////////// #include "hsWindows.h" #include "hsTypes.h" #include "hsMatrix44.h" #include "pnKeyedObject/hsKeyedObject.h" #include "plTextGenerator.h" #include "plGImage/plMipmap.h" #include "plPipeline/hsGDeviceRef.h" #include "pnMessage/plRefMsg.h" #include "plgDispatch.h" #include "hsResMgr.h" // Because tempKeys haven't been fixed yet (mf says to blame Eric Ellis), reffing // objects when we have a tempKey (or they have a tempKey) just don't work. In // fact, it will do nasty things like crashing on shutdown. Until then, we simply // won't do the refs. Note that this is BAD, but given the only time we currently // use these objects are very limited, controlled cases that *should* be okay // for now, we should be reasonably safe. For now. //#define MCN_DO_REFS //// Constructor & Destructor ///////////////////////////////////////////////// plTextGenerator::plTextGenerator() { fHost = nil; } plTextGenerator::plTextGenerator( plMipmap *host, UInt16 width, UInt16 height ) { fHost = nil; Attach( host, width, height ); } plTextGenerator::~plTextGenerator() { // This also won't work until tempKeys work, since the mipmap will be gone by // this time, in which case, calling Detach() crashes #ifdef MCN_DO_REFS Detach(); #endif } //// Attach /////////////////////////////////////////////////////////////////// // Grab onto a plMipmap, suck the texture out of it and replace it with our // own. void plTextGenerator::Attach( plMipmap *host, UInt16 width, UInt16 height ) { UInt16 textWidth, textHeight; hsAssert( fHost == nil, "Attempting to attach an already attached plTextGenerator" ); fHost = host; /// Suck the old texture data out fHost->Reset(); /// Make some new // Note that we need POW-2 textures, so we go for the next one up that will // fit what we need for( textWidth = 1; textWidth < width; textWidth <<= 1 ); for( textHeight = 1; textHeight < height; textHeight <<= 1 ); fWidth = width; fHeight = height; fHost->fImage = (void *)IAllocateOSSurface( textWidth, textHeight ); fHost->SetConfig( plMipmap::kARGB32Config ); fHost->fWidth = textWidth; fHost->fHeight = textHeight; fHost->fPixelSize = 32; fHost->fRowBytes = textWidth * 4; fHost->fNumLevels = 1; fHost->fFlags |= plMipmap::kUserOwnsBitmap | plMipmap::kDontThrowAwayImage; fHost->fCompressionType = plMipmap::kUncompressed; fHost->fUncompressedInfo.fType = plMipmap::UncompressedInfo::kRGB8888; fHost->IBuildLevelSizes(); fHost->fTotalSize = fHost->GetLevelSize( 0 ); // Destroy the old texture ref, since it's probably completely nutsoid at this point. // This should force the pipeline to recreate one more suitable for our use fHost->SetDeviceRef( nil ); // Some init color hsColorRGBA color; color.Set( 0.f, 0.f, 0.f, 1.f ); ClearToColor( color ); FlushToHost(); #ifdef MCN_DO_REFS /// Of course, brilliantly enough, if we did an attach on the constructor, we don't have a key /// yet, so we better give ourselves one before we can call AddViaNotify() if( GetKey() == nil ) { char str[ 256 ]; sprintf( str, "plTextGen:%s", fHost->GetKeyName() ); hsgResMgr::ResMgr()->NewKey( str, this, plLocation::kGlobalFixedLoc ); } /// Send ourselves a passive ref of the mipmap, so we get notified if and when it goes away hsgResMgr::ResMgr()->AddViaNotify( fHost->GetKey(), TRACKED_NEW plGenRefMsg( GetKey(), plRefMsg::kOnCreate, 0, 0 ), plRefFlags::kActiveRef ); #endif /// All done! } //// IAllocateOSSurface /////////////////////////////////////////////////////// // OS-specific. Allocates a rectangular bitmap of the given dimensions that // the OS can draw text into. Returns a pointer to the pixels. UInt32 *plTextGenerator::IAllocateOSSurface( UInt16 width, UInt16 height ) { #if HS_BUILD_FOR_WIN32 BITMAPINFO bmi; // Create a new DC and bitmap that we can draw characters to memset( &bmi.bmiHeader, 0, sizeof( BITMAPINFOHEADER ) ); bmi.bmiHeader.biSize = sizeof( BITMAPINFOHEADER ); bmi.bmiHeader.biWidth = width; bmi.bmiHeader.biHeight = -(int)height; bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biCompression = BI_RGB; bmi.bmiHeader.biBitCount = 32; fWinRGBDC = CreateCompatibleDC( nil ); fWinRGBBitmap = CreateDIBSection( fWinRGBDC, &bmi, DIB_RGB_COLORS, (void **)&fWinRGBBits, nil, 0 ); SetMapMode( fWinRGBDC, MM_TEXT ); SetBkMode( fWinRGBDC, TRANSPARENT ); SetTextAlign( fWinRGBDC, TA_TOP | TA_LEFT ); SelectObject( fWinRGBDC, fWinRGBBitmap ); // Now create a second DC/bitmap combo, this one for writing alpha values to memset( &bmi.bmiHeader, 0, sizeof( BITMAPINFOHEADER ) ); bmi.bmiHeader.biSize = sizeof( BITMAPINFOHEADER ); bmi.bmiHeader.biWidth = width; bmi.bmiHeader.biHeight = -(int)height; bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biCompression = BI_RGB; bmi.bmiHeader.biBitCount = 8; fWinAlphaDC = CreateCompatibleDC( nil ); fWinAlphaBitmap = CreateDIBSection( fWinAlphaDC, &bmi, DIB_RGB_COLORS, (void **)&fWinAlphaBits, nil, 0 ); SetMapMode( fWinAlphaDC, MM_TEXT ); SetBkMode( fWinAlphaDC, TRANSPARENT ); SetTextAlign( fWinAlphaDC, TA_TOP | TA_LEFT ); SelectObject( fWinAlphaDC, fWinAlphaBitmap ); return (UInt32 *)fWinRGBBits; #endif } //// Detach /////////////////////////////////////////////////////////////////// // Release the mipmap unto itself. void plTextGenerator::Detach( void ) { if( fHost == nil ) return; // hsAssert( fHost != nil, "Attempting to detach unattached host" ); SetFont( nil, 0 ); IDestroyOSSurface(); fHost->Reset(); fHost->fFlags &= ~( plMipmap::kUserOwnsBitmap | plMipmap::kDontThrowAwayImage ); // Destroy the old texture ref, since we're no longer using it fHost->SetDeviceRef( nil ); plMipmap *oldHost = fHost; fHost = nil; #ifdef MCN_DO_REFS // Now send ourselves a unref msg, just in case we were called directly (if this was done by // message, we'll get called a few times, but that's ok, we're set up to handle that, and it // won't happen 'cept on destruction so the speed penalty shouldn't be a problem) GetKey()->Release( oldHost->GetKey() ); #endif } //// IDestroyOSSurface //////////////////////////////////////////////////////// // Opposite of allocate. DUH! void plTextGenerator::IDestroyOSSurface( void ) { #if HS_BUILD_FOR_WIN32 fHost->fImage = nil; // DeleteObject() will get rid of it for us DeleteObject( fWinRGBBitmap ); DeleteDC( fWinRGBDC ); DeleteObject( fWinAlphaBitmap ); DeleteDC( fWinAlphaDC ); #endif } //// ClearToColor ///////////////////////////////////////////////////////////// void plTextGenerator::ClearToColor( hsColorRGBA &color ) { int i; UInt32 *data = (UInt32 *)fHost->fImage; UInt32 hexColor = color.ToARGB32(); #if HS_BUILD_FOR_WIN32 GdiFlush(); #endif for( i = 0; i < fHost->fWidth * fHost->fHeight; i++ ) data[ i ] = hexColor; // Fill our alpha bitmap as well, since we use that too #if HS_BUILD_FOR_WIN32 memset( fWinAlphaBits, (UInt8)( color.a * 255.f ), fHost->fWidth * fHost->fHeight ); #endif } //// SetFont ////////////////////////////////////////////////////////////////// // OS-specific. Load the given font for drawing the text with. void plTextGenerator::SetFont( const char *face, UInt16 size, hsBool antiAliasRGB ) { #if HS_BUILD_FOR_WIN32 if( fWinFont != nil ) { DeleteObject( fWinFont ); fWinFont = nil; } if( fWinAlphaFont != nil ) { DeleteObject( fWinAlphaFont ); fWinAlphaFont = nil; } if( face != nil ) { int nHeight = -MulDiv( size, GetDeviceCaps( fWinRGBDC, LOGPIXELSY ), 72 ); fWinFont = CreateFont( nHeight, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, antiAliasRGB ? ANTIALIASED_QUALITY : DEFAULT_QUALITY, VARIABLE_PITCH, face ); hsAssert( fWinFont != nil, "Cannot create Windows font for plTextGenerator" ); // The font for the alpha channel is identical except that it's antialiased, whereas the RGB version isn't. fWinAlphaFont = CreateFont( nHeight, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, (!antiAliasRGB) ? ANTIALIASED_QUALITY : DEFAULT_QUALITY, VARIABLE_PITCH, face ); hsAssert( fWinAlphaFont != nil, "Cannot create Windows font for plTextGenerator" ); SelectObject( fWinRGBDC, fWinFont ); SelectObject( fWinAlphaDC, fWinAlphaFont ); } #endif } //// SetTextColor ///////////////////////////////////////////////////////////// // blockRGB basically forces the RGB channel to write in blocks instead of // actual characters. This isn't useful unless you're relying on the alpha // channel to do the text (opaque text and transparent background), in which // case you want plenty of block color in your RGB channel because it'll get // alpha-ed out by the alpha channel. void plTextGenerator::SetTextColor( hsColorRGBA &color, hsBool blockRGB ) { #if HS_BUILD_FOR_WIN32 int r = (int)(color.r * 255.f); int g = (int)(color.g * 255.f); int b = (int)(color.b * 255.f); int a = (int)(color.a * 255.f); if( blockRGB ) { ::SetBkColor( fWinRGBDC, RGB( r, g, b ) ); ::SetBkMode( fWinRGBDC, OPAQUE ); } else ::SetBkMode( fWinRGBDC, TRANSPARENT ); ::SetTextColor( fWinRGBDC, RGB( r, g, b ) ); ::SetTextColor( fWinAlphaDC, RGB( a, a, a ) ); #endif } //// DrawString /////////////////////////////////////////////////////////////// void plTextGenerator::DrawString( UInt16 x, UInt16 y, const char *text ) { wchar_t *wText = hsStringToWString(text); DrawString(x,y,wText); delete [] wText; } void plTextGenerator::DrawString( UInt16 x, UInt16 y, const wchar_t *text ) { #if HS_BUILD_FOR_WIN32 ::TextOutW( fWinRGBDC, x, y, text, wcslen( text ) ); ::TextOutW( fWinAlphaDC, x, y, text, wcslen( text ) ); #endif } //// DrawClippedString //////////////////////////////////////////////////////// void plTextGenerator::DrawClippedString( Int16 x, Int16 y, const char *text, UInt16 width, UInt16 height ) { wchar_t *wText = hsStringToWString(text); DrawClippedString(x,y,wText,width,height); delete [] wText; } void plTextGenerator::DrawClippedString( Int16 x, Int16 y, const wchar_t *text, UInt16 width, UInt16 height ) { #if HS_BUILD_FOR_WIN32 RECT r; ::SetRect( &r, x, y, x + width, y + height ); ::ExtTextOutW( fWinRGBDC, x, y, ETO_CLIPPED, &r, text, wcslen( text ), nil ); ::ExtTextOutW( fWinAlphaDC, x, y, ETO_CLIPPED, &r, text, wcslen( text ), nil ); #endif } //// DrawClippedString //////////////////////////////////////////////////////// void plTextGenerator::DrawClippedString( Int16 x, Int16 y, const char *text, UInt16 clipX, UInt16 clipY, UInt16 width, UInt16 height ) { wchar_t *wText = hsStringToWString(text); DrawClippedString(x,y,wText,clipX,clipY,width,height); delete [] wText; } void plTextGenerator::DrawClippedString( Int16 x, Int16 y, const wchar_t *text, UInt16 clipX, UInt16 clipY, UInt16 width, UInt16 height ) { #if HS_BUILD_FOR_WIN32 RECT r; ::SetRect( &r, clipX, clipY, clipX + width, clipY + height ); ::ExtTextOutW( fWinRGBDC, x, y, ETO_CLIPPED, &r, text, wcslen( text ), nil ); ::ExtTextOutW( fWinAlphaDC, x, y, ETO_CLIPPED, &r, text, wcslen( text ), nil ); #endif } //// DrawWrappedString //////////////////////////////////////////////////////// void plTextGenerator::DrawWrappedString( UInt16 x, UInt16 y, const char *text, UInt16 width, UInt16 height ) { wchar_t *wText = hsStringToWString(text); DrawWrappedString(x,y,wText,width,height); delete [] wText; } void plTextGenerator::DrawWrappedString( UInt16 x, UInt16 y, const wchar_t *text, UInt16 width, UInt16 height ) { #if HS_BUILD_FOR_WIN32 RECT r; ::SetRect( &r, x, y, x + width, y + height ); // HBRUSH brush = ::CreateSolidBrush( RGB( 255, 255, 255 ) ); // ::FillRect( fWinRGBDC, &r, brush ); // ::DeleteObject( brush ); ::DrawTextW( fWinRGBDC, text, wcslen( text ), &r, DT_TOP | DT_LEFT | DT_NOPREFIX | DT_WORDBREAK ); ::DrawTextW( fWinAlphaDC, text, wcslen( text ), &r, DT_TOP | DT_LEFT | DT_NOPREFIX | DT_WORDBREAK ); #endif } //// CalcStringWidth ////////////////////////////////////////////////////////// UInt16 plTextGenerator::CalcStringWidth( const char *text, UInt16 *height ) { wchar_t *wText = hsStringToWString(text); UInt16 retVal = CalcStringWidth(wText,height); delete [] wText; return retVal; } UInt16 plTextGenerator::CalcStringWidth( const wchar_t *text, UInt16 *height ) { #if HS_BUILD_FOR_WIN32 SIZE size; ::GetTextExtentPoint32W( fWinRGBDC, text, wcslen( text ), &size ); if( height != nil ) *height = (UInt16)size.cy; return (UInt16)size.cx; #endif } //// CalcWrappedStringSize //////////////////////////////////////////////////// void plTextGenerator::CalcWrappedStringSize( const char *text, UInt16 *width, UInt16 *height ) { wchar_t *wText = hsStringToWString(text); CalcWrappedStringSize(wText,width,height); delete [] wText; } void plTextGenerator::CalcWrappedStringSize( const wchar_t *text, UInt16 *width, UInt16 *height ) { #if HS_BUILD_FOR_WIN32 RECT r; ::SetRect( &r, 0, 0, *width, 0 ); ::DrawTextW( fWinRGBDC, text, wcslen( text ), &r, DT_TOP | DT_LEFT | DT_NOPREFIX | DT_WORDBREAK | DT_CALCRECT ); *width = (UInt16)(r.right); if( height != nil ) *height = (UInt16)r.bottom; #endif } //// FillRect ///////////////////////////////////////////////////////////////// void plTextGenerator::FillRect( UInt16 x, UInt16 y, UInt16 width, UInt16 height, hsColorRGBA &color ) { #if HS_BUILD_FOR_WIN32 RECT rc; ::SetRect( &rc, x, y, x + width, y + height ); int r = (int)(color.r * 255.f); int g = (int)(color.g * 255.f); int b = (int)(color.b * 255.f); int a = (int)(color.a * 255.f); HBRUSH brush = ::CreateSolidBrush( RGB( r, g, b ) ); ::FillRect( fWinRGBDC, &rc, brush ); ::DeleteObject( brush ); brush = ::CreateSolidBrush( RGB( a, a, a ) ); ::FillRect( fWinAlphaDC, &rc, brush ); ::DeleteObject( brush ); #endif } //// FrameRect //////////////////////////////////////////////////////////////// void plTextGenerator::FrameRect( UInt16 x, UInt16 y, UInt16 width, UInt16 height, hsColorRGBA &color ) { #if HS_BUILD_FOR_WIN32 RECT rc; ::SetRect( &rc, x, y, x + width, y + height ); int r = (int)(color.r * 255.f); int g = (int)(color.g * 255.f); int b = (int)(color.b * 255.f); int a = (int)(color.a * 255.f); HBRUSH brush = ::CreateSolidBrush( RGB( r, g, b ) ); ::FrameRect( fWinRGBDC, &rc, brush ); ::DeleteObject( brush ); brush = ::CreateSolidBrush( RGB( a, a, a ) ); ::FrameRect( fWinAlphaDC, &rc, brush ); ::DeleteObject( brush ); #endif } //// FlushToHost ////////////////////////////////////////////////////////////// void plTextGenerator::FlushToHost( void ) { #if HS_BUILD_FOR_WIN32 // Flush the GDI first, to make sure it's done GdiFlush(); // Now copy our alpha channel over. I hate the GDI UInt32 i = fHost->fWidth * fHost->fHeight; UInt32 *dest = fWinRGBBits; UInt8 *src = fWinAlphaBits; /* while( i-- ) { *dest &= 0x00ffffff; *dest |= ( *src ) << 24; // *dest |= ( *dest << 16 ) & 0xff000000; dest++; src++; } */ do { i--; dest[ i ] &= 0x00ffffff; dest[ i ] |= src[ i ] << 24; } while( i ); #endif // Dirty the mipmap's deviceRef, if there is one if( fHost->GetDeviceRef() != nil ) fHost->GetDeviceRef()->SetDirty( true ); } //// GetTextWidth/Height ////////////////////////////////////////////////////// UInt16 plTextGenerator::GetTextWidth( void ) { return ( fHost != nil ) ? (UInt16)(fHost->fWidth) : 0; } UInt16 plTextGenerator::GetTextHeight( void ) { return ( fHost != nil ) ? (UInt16)(fHost->fHeight) : 0; } //// GetLayerTransform //////////////////////////////////////////////////////// // Since the textGen can actually create a texture bigger than you were expecting, // you want to be able to apply a layer texture transform that will compensate. This // function will give you that transform. Just feed it into plLayer->SetTransform(). hsMatrix44 plTextGenerator::GetLayerTransform( void ) { hsMatrix44 xform; hsVector3 scale; scale.Set( (float)GetWidth() / (float)GetTextWidth(), (float)GetHeight() / (float)GetTextHeight(), 1.f ); xform.MakeScaleMat( &scale ); return xform; } //// MsgReceive /////////////////////////////////////////////////////////////// hsBool plTextGenerator::MsgReceive( plMessage *msg ) { #ifdef MCN_DO_REFS plGenRefMsg *refMsg = plGenRefMsg::ConvertNoRef( msg ); if( refMsg != nil ) { if( refMsg->GetContext() & ( plRefMsg::kOnCreate | plRefMsg::kOnRequest ) ) { // Don't do anything--already did an attach } else if( refMsg->GetContext() & ( plRefMsg::kOnDestroy | plRefMsg::kOnRemove ) ) { Detach(); } return true; } #endif return hsKeyedObject::MsgReceive( msg ); }