//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #define SUPPORT_CUSTOM_FONT_FORMAT #ifdef SUPPORT_CUSTOM_FONT_FORMAT #define _WIN32_WINNT 0x0500 #endif #include "gameuischeme.h" #include "gameuisystemsurface.h" #include "vgui/ISystem.h" #include "tier1/utlbuffer.h" #include "gameuisystemmgr.h" #include "tier1/KeyValues.h" #include "vgui_surfacelib/fontmanager.h" #include "vgui/isurface.h" #include "vgui_controls/controls.h" // has system() #if defined( _X360 ) #include "xbox/xbox_win32stubs.h" #endif #include "xbox/xboxstubs.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #define FONT_ALIAS_NAME_LENGTH 64 CGameUISchemeManager g_GameUISchemeManager; CGameUISchemeManager *g_pGameUISchemeManager = &g_GameUISchemeManager; //----------------------------------------------------------------------------- // Helper function for getting the language the game ui should use. //----------------------------------------------------------------------------- static void HelperGetLanguage( char *pLanguageBuf, int bufSize ) { bool bValid = false; if ( IsPC() ) { bValid = vgui::system()->GetRegistryString( "HKEY_CURRENT_USER\\Software\\Valve\\Steam\\Language", pLanguageBuf, bufSize - 1 ); } else { Q_strncpy( pLanguageBuf, XBX_GetLanguageString(), bufSize ); bValid = true; } if ( !bValid ) { Q_strncpy( pLanguageBuf, "english", bufSize ); } } //----------------------------------------------------------------------------- // Constructor //----------------------------------------------------------------------------- CGameUISchemeManager::CGameUISchemeManager() { // 0th element is null, since that would be an invalid handle CGameUIScheme *nullScheme = new CGameUIScheme(); m_Schemes.AddToTail(nullScheme); Assert( g_pGameUISystemSurface ); } //----------------------------------------------------------------------------- // Purpose: Destructor //----------------------------------------------------------------------------- CGameUISchemeManager::~CGameUISchemeManager() { int i; for ( i = 0; i < m_Schemes.Count(); i++ ) { delete m_Schemes[i]; m_Schemes[i] = NULL; } m_Schemes.RemoveAll(); Shutdown( false ); } //----------------------------------------------------------------------------- // Purpose: Reloads the schemes from the files //----------------------------------------------------------------------------- void CGameUISchemeManager::ReloadSchemes() { int count = m_Schemes.Count(); Shutdown( false ); // reload the scheme for (int i = 1; i < count; i++) { LoadSchemeFromFile( m_Schemes[i]->GetFileName(), m_Schemes[i]->GetName() ); } } //----------------------------------------------------------------------------- // Purpose: Reload the fonts in all schemes //----------------------------------------------------------------------------- void CGameUISchemeManager::ReloadFonts( int inScreenTall ) { for (int i = 1; i < m_Schemes.Count(); i++) { m_Schemes[i]->ReloadFontGlyphs( inScreenTall ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CGameUISchemeManager::Shutdown( bool full ) { // Full shutdown kills the null scheme for( int i = full ? 0 : 1; i < m_Schemes.Count(); i++ ) { m_Schemes[i]->Shutdown( full ); } if ( full ) { m_Schemes.RemoveAll(); } } //----------------------------------------------------------------------------- // Purpose: Find an already loaded scheme //----------------------------------------------------------------------------- IGameUIScheme * CGameUISchemeManager::FindLoadedScheme( const char *pFilename ) { // Find the scheme in the list of already loaded schemes for ( int i = 1; i < m_Schemes.Count(); i++ ) { char const *schemeFileName = m_Schemes[i]->GetFileName(); if ( !stricmp( schemeFileName, pFilename ) ) return m_Schemes[i]; } return NULL; } //----------------------------------------------------------------------------- // First scheme loaded becomes the default scheme, // and all subsequent loaded scheme are derivatives of that //----------------------------------------------------------------------------- IGameUIScheme * CGameUISchemeManager::LoadSchemeFromFileEx( const char *pFilename, const char *tag ) { // Look to see if we've already got this scheme... CGameUIScheme * hScheme = ( CGameUIScheme * ) FindLoadedScheme( pFilename ); if ( hScheme ) { if ( hScheme->IsActive() ) { if ( IsPC() ) { hScheme->ReloadFontGlyphs(); } return hScheme; } } else { hScheme = new CGameUIScheme(); m_Schemes.AddToTail( hScheme ); } KeyValues *data; data = new KeyValues("Scheme"); data->UsesEscapeSequences( true ); // VGUI uses this // look first in skins directory bool result = data->LoadFromFile( g_pFullFileSystem, pFilename, "SKIN" ); if (!result) { result = data->LoadFromFile( g_pFullFileSystem, pFilename, "GAME" ); if ( !result ) { // look in any directory result = data->LoadFromFile( g_pFullFileSystem, pFilename, NULL ); } } if (!result) { data->deleteThis(); return 0; } if ( IsPC() ) { ConVarRef cl_hud_minmode( "cl_hud_minmode", true ); if ( cl_hud_minmode.IsValid() && cl_hud_minmode.GetBool() ) { data->ProcessResolutionKeys( "_minmode" ); } } hScheme->SetActive( true ); hScheme->LoadFromFile( pFilename, tag, data ); return hScheme; } //----------------------------------------------------------------------------- // Purpose: loads a scheme from disk //----------------------------------------------------------------------------- IGameUIScheme * CGameUISchemeManager::LoadSchemeFromFile( const char *fileName, const char *tag ) { return LoadSchemeFromFileEx( fileName, tag ); } //----------------------------------------------------------------------------- // Purpose: returns a handle to the default (first loaded) scheme //----------------------------------------------------------------------------- IGameUIScheme * CGameUISchemeManager::GetDefaultScheme() { if ( m_Schemes.Count() >= 2 ) return m_Schemes[1]; else if ( m_Schemes.Count() > 0 ) return m_Schemes[0]; else return NULL; } //----------------------------------------------------------------------------- // Purpose: returns a handle to the scheme identified by "tag" //----------------------------------------------------------------------------- IGameUIScheme * CGameUISchemeManager::GetScheme( const char *tag ) { for ( int i=1; iGetName() ) ) { return m_Schemes[i]; } } return GetDefaultScheme(); // default scheme } //----------------------------------------------------------------------------- // gets the proportional coordinates for doing screen-size independant panel layouts // use these for font, image and panel size scaling (they all use the pixel height of the display for scaling) //----------------------------------------------------------------------------- int CGameUISchemeManager::GetProportionalScaledValueEx( CGameUIScheme *pScheme, int normalizedValue, int screenTall ) { return GetProportionalScaledValue( normalizedValue, screenTall ); } //----------------------------------------------------------------------------- // Purpose: converts a value into proportional mode //----------------------------------------------------------------------------- int CGameUISchemeManager::GetProportionalScaledValue( int normalizedValue, int screenTall ) { int tall; if ( screenTall == -1 ) { g_pGameUISystemMgrImpl->GetScreenHeightForFontLoading( tall ); } else { tall = screenTall; } return GetProportionalScaledValue_( tall, normalizedValue ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- int CGameUISchemeManager::GetProportionalScaledValue_( int screenTall, int normalizedValue ) { int baseWide, baseTall; g_pGameUISystemSurface->GetProportionalBase( baseWide, baseTall ); double scale = (double)screenTall / (double)baseTall; return (int)( normalizedValue * scale ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- void CGameUISchemeManager::SpewFonts( void ) { for ( int i = 1; i < m_Schemes.Count(); i++ ) { m_Schemes[i]->SpewFonts(); } } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- void CGameUISchemeManager::SetLanguage( const char *pLanguage ) { g_pGameUISystemSurface->SetLanguage( pLanguage ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- const char *CGameUISchemeManager::GetLanguage() { return g_pGameUISystemSurface->GetLanguage(); } //----------------------------------------------------------------------------- // Constructor //----------------------------------------------------------------------------- CGameUIScheme::CGameUIScheme() { m_pFileName = ""; m_pTag = ""; m_pData = NULL; SetActive( false ); } CGameUIScheme::~CGameUIScheme() { } //----------------------------------------------------------------------------- // Purpose: loads a scheme from from disk into memory //----------------------------------------------------------------------------- void CGameUIScheme::LoadFromFile( const char *pFilename, const char *inTag, KeyValues *inKeys ) { COM_TimestampedLog( "CScheme::LoadFromFile( %s )", pFilename ); m_pFileName = pFilename; m_pData = inKeys; // override the scheme name with the tag name KeyValues *name = m_pData->FindKey( "Name", true ); name->SetString("Name", inTag); if ( inTag ) { m_pTag = inTag; } else { Error( "You need to name the scheme (%s)!", m_pFileName.Get() ); m_pTag = "default"; } LoadFonts(); } //----------------------------------------------------------------------------- // Purpose: Set the range of character values this font can be used on //----------------------------------------------------------------------------- bool CGameUIScheme::GetFontRange( const char *fontname, int &nMin, int &nMax ) { for ( int i = 0 ; i < m_FontRanges.Count() ; i++ ) { if ( Q_stricmp( m_FontRanges[i]._fontName.String(), fontname ) == 0 ) { nMin = m_FontRanges[i]._min; nMax = m_FontRanges[i]._max; return true; } } return false; } //----------------------------------------------------------------------------- // Purpose: Get the range of character values this font can be used on //----------------------------------------------------------------------------- void CGameUIScheme::SetFontRange( const char *fontname, int nMin, int nMax ) { for ( int i = 0 ; i < m_FontRanges.Count() ; i++ ) { if ( Q_stricmp( m_FontRanges[i]._fontName.String(), fontname ) == 0 ) { m_FontRanges[i]._min = nMin; m_FontRanges[i]._max = nMax; return; } } // not already in our list int iIndex = m_FontRanges.AddToTail(); m_FontRanges[iIndex]._fontName = fontname; m_FontRanges[iIndex]._min = nMin; m_FontRanges[iIndex]._max = nMax; } //----------------------------------------------------------------------------- // Purpose: adds all the font specifications to the surface //----------------------------------------------------------------------------- void CGameUIScheme::LoadFonts() { // get our language const char *pLanguage = g_GameUISchemeManager.GetLanguage(); // add our custom fonts for ( KeyValues *kv = m_pData->FindKey("CustomFontFiles", true)->GetFirstSubKey(); kv != NULL; kv = kv->GetNextKey() ) { const char *fontFile = kv->GetString(); if (fontFile && *fontFile) { g_pGameUISystemSurface->AddCustomFontFile( fontFile ); } else { // we have a block to read int nRangeMin = 0, nRangeMax = 0; const char *pszName = NULL; bool bUseRange = false; for ( KeyValues *pData = kv->GetFirstSubKey(); pData != NULL; pData = pData->GetNextKey() ) { const char *pszKey = pData->GetName(); if ( !Q_stricmp( pszKey, "font" ) ) { fontFile = pData->GetString(); } else if ( !Q_stricmp( pszKey, "name" ) ) { pszName = pData->GetString(); } else { // we must have a language if ( Q_stricmp( pLanguage, pszKey ) == 0 ) // matches the language we're running? { // get the range KeyValues *pRange = pData->FindKey( "range" ); if ( pRange ) { bUseRange = true; sscanf( pRange->GetString(), "%x %x", &nRangeMin, &nRangeMax ); if ( nRangeMin > nRangeMax ) { int nTemp = nRangeMin; nRangeMin = nRangeMax; nRangeMax = nTemp; } } } } } if ( fontFile && *fontFile ) { g_pGameUISystemSurface->AddCustomFontFile( fontFile ); if ( bUseRange ) { SetFontRange( pszName, nRangeMin, nRangeMax ); } } } } // add bitmap fonts for ( KeyValues *kv = m_pData->FindKey("BitmapFontFiles", true)->GetFirstSubKey(); kv != NULL; kv = kv->GetNextKey() ) { const char *fontFile = kv->GetString(); if (fontFile && *fontFile) { bool bSuccess = g_pGameUISystemSurface->AddBitmapFontFile( fontFile ); if ( bSuccess ) { // refer to the font by a user specified symbol g_pGameUISystemSurface->SetBitmapFontName( kv->GetName(), fontFile ); } } } // create the fonts for (KeyValues *kv = m_pData->FindKey("Fonts", true)->GetFirstSubKey(); kv != NULL; kv = kv->GetNextKey()) { for ( int i = 0; i < 2; i++ ) { // create the base font bool proportionalFont = i ? true : false; const char *fontName = GetMungedFontName( kv->GetName(), m_pTag.Get(), proportionalFont ); // first time it adds a normal font, and then a proportional one FontHandle_t font = g_pGameUISystemSurface->CreateFont(); int j = m_FontAliases.AddToTail(); m_FontAliases[j]._fontName = fontName; m_FontAliases[j]._trueFontName = kv->GetName(); m_FontAliases[j]._font = font; m_FontAliases[j].m_bProportional = proportionalFont; } } // load in the font glyphs ReloadFontGlyphs(); } //----------------------------------------------------------------------------- // Purpose: Reloads all scheme fonts // Supply a language if you don't want to use the one in the registry. //----------------------------------------------------------------------------- void CGameUIScheme::ReloadFontGlyphs( int inScreenTall ) { int screenTall; // get our current resolution if ( inScreenTall == -1 ) { //int screenWide; g_pGameUISystemMgrImpl->GetScreenHeightForFontLoading( screenTall ); } else // this will make fonts be the correct size in the editor, where the stage size is the screen size. { screenTall = inScreenTall; } // check our language; some have minimum sizes int minimumFontHeight = GetMinimumFontHeightForCurrentLanguage( g_GameUISchemeManager.GetLanguage() ); // add the data to all the fonts KeyValues *fonts = m_pData->FindKey( "Fonts", true ); for (int i = 0; i < m_FontAliases.Count(); i++) { KeyValues *kv = fonts->FindKey( m_FontAliases[i]._trueFontName.String(), true ); // walk through creating adding the first matching glyph set to the font for ( KeyValues *fontdata = kv->GetFirstSubKey(); fontdata != NULL; fontdata = fontdata->GetNextKey() ) { // skip over fonts not meant for this resolution int fontYResMin = 0, fontYResMax = 0; sscanf( fontdata->GetString( "yres", "" ), "%d %d", &fontYResMin, &fontYResMax ); if ( fontYResMin ) { if ( !fontYResMax ) { fontYResMax = fontYResMin; } // check the range if ( screenTall < fontYResMin || screenTall > fontYResMax ) continue; } int flags = 0; if (fontdata->GetInt( "italic" )) { flags |= FONTFLAG_ITALIC; } if (fontdata->GetInt( "underline" )) { flags |= FONTFLAG_UNDERLINE; } if (fontdata->GetInt( "strikeout" )) { flags |= FONTFLAG_STRIKEOUT; } if (fontdata->GetInt( "symbol" )) { flags |= FONTFLAG_SYMBOL; } if (fontdata->GetInt( "antialias" ) && g_pGameUISystemSurface->SupportsFontFeature( FONT_FEATURE_ANTIALIASED_FONTS ) ) { flags |= FONTFLAG_ANTIALIAS; } if (fontdata->GetInt( "dropshadow" ) && g_pGameUISystemSurface->SupportsFontFeature( FONT_FEATURE_DROPSHADOW_FONTS ) ) { flags |= FONTFLAG_DROPSHADOW; } if (fontdata->GetInt( "outline" ) && g_pGameUISystemSurface->SupportsFontFeature( FONT_FEATURE_OUTLINE_FONTS ) ) { flags |= FONTFLAG_OUTLINE; } if (fontdata->GetInt( "custom" )) { flags |= FONTFLAG_CUSTOM; } if (fontdata->GetInt( "bitmap" )) { flags |= FONTFLAG_BITMAP; } if (fontdata->GetInt( "rotary" )) { flags |= FONTFLAG_ROTARY; } if (fontdata->GetInt( "additive" )) { flags |= FONTFLAG_ADDITIVE; } int fontTall = fontdata->GetInt( "tall" ); int blur = fontdata->GetInt( "blur" ); int scanlines = fontdata->GetInt( "scanlines" ); float scalex = fontdata->GetFloat( "scalex", 1.0f ); float scaley = fontdata->GetFloat( "scaley", 1.0f ); // only grow this font if it doesn't have a resolution filter specified if ( ( !fontYResMin && !fontYResMax ) && m_FontAliases[i].m_bProportional ) { fontTall = g_GameUISchemeManager.GetProportionalScaledValueEx( this, fontTall, screenTall ); blur = g_GameUISchemeManager.GetProportionalScaledValueEx( this, blur ); scanlines = g_GameUISchemeManager.GetProportionalScaledValueEx( this, scanlines ); scalex = g_GameUISchemeManager.GetProportionalScaledValueEx( this, scalex * 10000.0f ) * 0.0001f; scaley = g_GameUISchemeManager.GetProportionalScaledValueEx( this, scaley * 10000.0f ) * 0.0001f; } // clip the font size so that fonts can't be too big if ( fontTall > 127 ) { fontTall = 127; } // check our minimum font height if ( fontTall < minimumFontHeight ) { fontTall = minimumFontHeight; } if ( flags & FONTFLAG_BITMAP ) { // add the new set g_pGameUISystemSurface->SetBitmapFontGlyphSet( m_FontAliases[i]._font, g_pGameUISystemSurface->GetBitmapFontName( fontdata->GetString( "name" ) ), scalex, scaley, flags); } else { int nRangeMin, nRangeMax; if ( GetFontRange( fontdata->GetString( "name" ), nRangeMin, nRangeMax ) ) { // add the new set g_pGameUISystemSurface->SetFontGlyphSet( m_FontAliases[i]._font, fontdata->GetString( "name" ), fontTall, fontdata->GetInt( "weight" ), blur, scanlines, flags, nRangeMin, nRangeMax); } else { // add the new set g_pGameUISystemSurface->SetFontGlyphSet( m_FontAliases[i]._font, fontdata->GetString( "name" ), fontTall, fontdata->GetInt( "weight" ), blur, scanlines, flags); } } // don't add any more break; } } } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- void CGameUIScheme::SpewFonts( void ) { Msg( "Game UI Scheme: %s (%s)\n", GetName(), GetFileName() ); for ( int i = 0; i < m_FontAliases.Count(); i++ ) { Msg( " %2d: HFont:0x%8.8x, %s, %s, font:%s, tall:%d\n", i, m_FontAliases[i]._font, m_FontAliases[i]._trueFontName.String(), m_FontAliases[i]._fontName.String(), g_pGameUISystemSurface->GetFontName( m_FontAliases[i]._font ), g_pGameUISystemSurface->GetFontTall( m_FontAliases[i]._font ) ); } } //----------------------------------------------------------------------------- // Purpose: Kills the scheme //----------------------------------------------------------------------------- void CGameUIScheme::Shutdown( bool full ) { SetActive( false ); m_FontRanges.RemoveAll(); if (m_pData) { m_pData->deleteThis(); m_pData = NULL; } if ( full ) { delete this; } } //----------------------------------------------------------------------------- // Finds a font in the alias list //----------------------------------------------------------------------------- FontHandle_t CGameUIScheme::FindFontInAliasList( const char *fontName ) { // FIXME: Slow!!! for (int i = m_FontAliases.Count(); --i >= 0; ) { if ( !strnicmp( fontName, m_FontAliases[i]._fontName.String(), FONT_ALIAS_NAME_LENGTH ) ) return m_FontAliases[i]._font; } // No dice return 0; } //----------------------------------------------------------------------------- // Purpose: // Input : font - // Output : char const //----------------------------------------------------------------------------- char const *CGameUIScheme::GetFontName( const FontHandle_t &font ) { for (int i = m_FontAliases.Count(); --i >= 0; ) { FontHandle_t fnt = ( FontHandle_t )m_FontAliases[i]._font; if ( fnt == font ) return m_FontAliases[i]._trueFontName.String(); } return ""; } //----------------------------------------------------------------------------- // material Setting methods //----------------------------------------------------------------------------- FontHandle_t CGameUIScheme::GetFont( const char *fontName, bool proportional ) { // First look in the list of aliases... return FindFontInAliasList( GetMungedFontName( fontName, m_pTag.Get(), proportional ) ); } //----------------------------------------------------------------------------- // Return the vgui name of the font that is the same truefont name and one size up. // bUp is if true you want the next size up or the next size down. //----------------------------------------------------------------------------- FontHandle_t CGameUIScheme::GetFontNextSize( bool bUp, const char *fontName, bool proportional ) { FontHandle_t currentFontHandle = GetFont( fontName, proportional ); // Current font name wasn't found. if ( currentFontHandle == 0 ) { return currentFontHandle; } int currentFontTall = g_pGameUISystemSurface->GetFontTall( currentFontHandle ); const char* pCurrentFontName = g_pGameUISystemSurface->GetFontName( currentFontHandle ); // Now find the closest font with this name that is the smallest size up int sizeDifference = INT_MAX; FontHandle_t closestFontHandle = 0; for (int i = m_FontAliases.Count(); --i > 0; ) { if ( proportional != m_FontAliases[i].m_bProportional ) continue; FontHandle_t testFontHandle = m_FontAliases[i]._font; const char* pTestFontName = g_pGameUISystemSurface->GetFontName( testFontHandle ); if ( currentFontHandle != testFontHandle ) { if ( !Q_stricmp( pCurrentFontName, pTestFontName ) ) // font name is the same. { int thisFontTall = g_pGameUISystemSurface->GetFontTall( testFontHandle ); int diff; if ( bUp ) // get next size up { diff = thisFontTall - currentFontTall; } else // get next size down. { diff = currentFontTall - thisFontTall; } if ( diff > 0 && diff < sizeDifference ) { sizeDifference = diff; closestFontHandle = testFontHandle; } } } } if ( closestFontHandle != 0 ) { return closestFontHandle; } else { return currentFontHandle; } } //----------------------------------------------------------------------------- // Purpose: returns a char string of the munged name this font is stored as in the font manager //----------------------------------------------------------------------------- const char *CGameUIScheme::GetMungedFontName( const char *fontName, const char *scheme, bool proportional ) { static char mungeBuffer[ 64 ]; if ( scheme ) { Q_snprintf( mungeBuffer, sizeof( mungeBuffer ), "%s%s-%s", fontName, scheme, proportional ? "p" : "no" ); } else { Q_snprintf( mungeBuffer, sizeof( mungeBuffer ), "%s-%s", fontName, proportional ? "p" : "no" ); // we don't want the "(null)" snprintf appends } return mungeBuffer; } //----------------------------------------------------------------------------- // Purpose: gets the minimum font height for the current language //----------------------------------------------------------------------------- int CGameUIScheme::GetMinimumFontHeightForCurrentLanguage( const char *pLanguage ) { char language[64]; bool bValid; if ( IsPC() ) { if ( pLanguage ) { Q_strncpy( language, pLanguage, sizeof(language)-1 ); bValid = true; } else { bValid = vgui::system()->GetRegistryString( "HKEY_CURRENT_USER\\Software\\Valve\\Steam\\Language", language, sizeof(language)-1 ); } } else { Q_strncpy( language, XBX_GetLanguageString(), sizeof( language ) ); bValid = true; } if ( bValid ) { if (!stricmp(language, "korean") || !stricmp(language, "koreana") || !stricmp(language, "tchinese") || !stricmp(language, "schinese") || !stricmp(language, "japanese")) { // the bitmap-based fonts for these languages simply don't work with a pt. size of less than 9 (13 pixels) return 13; } if ( !stricmp(language, "thai" ) ) { // thai has problems below 18 pts return 18; } } // no special minimum height required return 0; } void CGameUIScheme::SetActive( bool bActive ) { m_bActive = bActive; } bool CGameUIScheme::IsActive() const { return m_bActive; }