|
|
//========= Copyright 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=====================================================================================//
#include <locale.h>
#include "vgui_surfacelib/BitmapFont.h"
#include "vgui_surfacelib/fontmanager.h"
#include "convar.h"
#include <vgui/ISurface.h>
#include <tier0/dbg.h>
// memdbgon must be the last include file in a .cpp file!!!
#include <tier0/memdbgon.h>
static CFontManager s_FontManager;
#ifdef WIN32
extern bool s_bSupportsUnicode; #endif
#if !defined( _X360 )
#define MAX_INITIAL_FONTS 100
#else
#define MAX_INITIAL_FONTS 1
#endif
//-----------------------------------------------------------------------------
// Purpose: singleton accessor
//-----------------------------------------------------------------------------
CFontManager &FontManager() { return s_FontManager; }
//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
CFontManager::CFontManager() { // add a single empty font, to act as an invalid font handle 0
m_FontAmalgams.EnsureCapacity( MAX_INITIAL_FONTS ); m_FontAmalgams.AddToTail(); m_Win32Fonts.EnsureCapacity( MAX_INITIAL_FONTS );
#ifdef LINUX
FT_Error error = FT_Init_FreeType( &library ); if ( error ) Error( "Unable to initalize freetype library, is it installed?" ); pFontDataHelper = NULL; #endif
// setup our text locale
setlocale( LC_CTYPE, "" ); setlocale( LC_TIME, "" ); setlocale( LC_COLLATE, "" ); setlocale( LC_MONETARY, "" );
m_pFileSystem = NULL; m_pMaterialSystem = NULL; }
//-----------------------------------------------------------------------------
// Purpose: language setting for font fallbacks
//-----------------------------------------------------------------------------
void CFontManager::SetLanguage(const char *language) { Q_strncpy(m_szLanguage, language, sizeof(m_szLanguage)); }
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
const char *CFontManager::GetLanguage() { return m_szLanguage; }
//-----------------------------------------------------------------------------
// Purpose: Destructor
//-----------------------------------------------------------------------------
CFontManager::~CFontManager() { ClearAllFonts(); #ifdef LINUX
FT_Done_FreeType( library ); #endif
}
//-----------------------------------------------------------------------------
// Purpose: frees the fonts
//-----------------------------------------------------------------------------
void CFontManager::ClearAllFonts() { // free the fonts
for (int i = 0; i < m_Win32Fonts.Count(); i++) { delete m_Win32Fonts[i]; } m_Win32Fonts.RemoveAll();
m_FontAmalgams.RemoveAll(); }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
vgui::HFont CFontManager::CreateFont() { int i = m_FontAmalgams.AddToTail(); return i; }
//-----------------------------------------------------------------------------
// Purpose: Sets the valid glyph ranges for a font created by CreateFont()
//-----------------------------------------------------------------------------
bool CFontManager::SetFontGlyphSet(HFont font, const char *windowsFontName, int tall, int weight, int blur, int scanlines, int flags) { return SetFontGlyphSet( font, windowsFontName, tall, weight, blur, scanlines, flags, 0, 0); }
//-----------------------------------------------------------------------------
// Purpose: Sets the valid glyph ranges for a font created by CreateFont()
//-----------------------------------------------------------------------------
bool CFontManager::SetFontGlyphSet(HFont font, const char *windowsFontName, int tall, int weight, int blur, int scanlines, int flags, int nRangeMin, int nRangeMax) { // ignore all but the first font added
// need to rev vgui versions and change the name of this function
if ( m_FontAmalgams[font].GetCount() > 0 ) { // clear any existing fonts
m_FontAmalgams[font].RemoveAll(); }
bool bForceSingleFont = false; if ( IsX360() ) { //-----//
// 360 //
//-----//
// AV - The 360 must use the same size font for 0-255 and 256-0xFFFF regardless of the font since the
// fontAmalgam can only deal with a consistent font height for all fonts in a single amalgam. We
// could change this if we forced all fonts within a single amalgam to have the same height with
// the font baselines aligned, but even then the fonts wouldn't look great, because different
// fonts set to the same size don't necessarily have the same height visually. We need to revisit
// this before shipping l4d2 on the PC!
bForceSingleFont = true;
// discovered xbox only allows glyphs from these languages from the foreign fallback font
// prefer to have the entire range of chars from the font so UI doesn't suffer from glyph disparity
if ( !V_stricmp( windowsFontName, "toolbox" ) || !V_stricmp( windowsFontName, "courier new" ) ) { // toolbox stays as-is
// courier new is an internal debug font, not part of customer UI, need it stay as is
} else { bool bUseFallback = false; if ( !V_stricmp( m_szLanguage, "portuguese" ) || !V_stricmp( m_szLanguage, "polish" ) ) { static ConVarRef mat_xbox_iswidescreen( "mat_xbox_iswidescreen" ); static ConVarRef mat_xbox_ishidef( "mat_xbox_ishidef" );
// we can support these languages with our desired fonts in hidef/widescreen modes only
// we must fallback to the more legible font in the lowdef or non-widescreen
bUseFallback = !( mat_xbox_iswidescreen.GetBool() && mat_xbox_ishidef.GetBool() ); }
if ( bUseFallback || !V_stricmp( m_szLanguage, "japanese" ) || !V_stricmp( m_szLanguage, "korean" ) || !V_stricmp( m_szLanguage, "schinese" ) || !V_stricmp( m_szLanguage, "tchinese" ) || !V_stricmp( m_szLanguage, "russian" ) ) { // these languages must use the font that has their glyphs
// these language require a high degree of legibility
windowsFontName = GetForeignFallbackFontName(); } } } else { //----//
// PC //
//----//
// AV - The PC has the same issues caused by multiple fonts in a single amalgam with different font
// heights...see comment above. Given the available languages in Steam, the languages below
// were illegible at 1024x768. Resolutions of 800x600 and 640x480 are a complete mess for all
// languages, including English. We probably need to fallback to Tahoma for all languages when
// the vertical resolution < 720. This will probably be the next check-in, but we need to evaluate
// this further tomorrow.
if ( !V_stricmp( windowsFontName, "toolbox" ) || !V_stricmp( windowsFontName, "courier new" ) ) { // toolbox stays as-is
// courier new is an internal debug font, not part of customer UI, need it stay as is
} else { // These languages are illegible @ vertical resolutions <= 768
if ( !V_stricmp( m_szLanguage, "korean" ) || !V_stricmp( m_szLanguage, "schinese" ) || !V_stricmp( m_szLanguage, "tchinese" ) || !V_stricmp( m_szLanguage, "russian" ) || !V_stricmp( m_szLanguage, "thai" ) || !V_stricmp( m_szLanguage, "japanese" ) || !V_stricmp( m_szLanguage, "czech" ) ) { windowsFontName = GetForeignFallbackFontName(); bForceSingleFont = true; } } }
// AV - If we actually want to support multiple fonts within an amalgam, we need a change here. Currently,
// the code will use winFont for 0-255 and pExtendedFont for 256-0xFFFF! But since the functions for
// getting the font height from the amalgam can only return one height, the heights of the two fonts
// need to be identical. This isn't trivial because even if we query both fonts for their height
// first, that won't force their baselines within the font pages to be aligned. So we would have to
// do something much more complicated where we loop over all characters in each font to find the
// absolute ascent and descent above/below the baseline and then when we create the font, align both
// fonts to the shared baseline with a shared height. But even with the font baselines aligned with a
// shared height, the fonts still wouldn't look great, because different fonts set to the same size
// don't necessarily have the same height visually. We need to revisit this before shipping l4d2 on the PC!
// And there are still issues with what I'm suggesting here because when we ask the font API what
// the maxHeight, maxAscent, maxDescent is, we get inconsistent results, so I'm not even sure we could
// successfully align the baseline of two fonts in the font pages.
font_t *winFont = CreateOrFindWin32Font( windowsFontName, tall, weight, blur, scanlines, flags );
// cycle until valid english/extended font support has been created
do { // add to the amalgam
if ( bForceSingleFont || IsFontForeignLanguageCapable( windowsFontName ) ) { if ( winFont ) { // font supports the full range of characters
m_FontAmalgams[font].AddFont( winFont, 0x0000, 0xFFFF ); return true; } } else { // font cannot provide glyphs and just supports the normal range
// redirect to a font that can supply glyps
const char *localizedFontName = GetForeignFallbackFontName(); if ( winFont && !stricmp( localizedFontName, windowsFontName ) ) { // it's the same font and can support the full range
m_FontAmalgams[font].AddFont( winFont, 0x0000, 0xFFFF ); return true; }
// create the extended support font
font_t *pExtendedFont = CreateOrFindWin32Font( localizedFontName, tall, weight, blur, scanlines, flags ); if ( winFont && pExtendedFont ) { // use the normal font for english characters, and the extended font for the rest
int nMin = 0x0000, nMax = 0x00FF;
// did we specify a range?
if ( nRangeMin > 0 || nRangeMax > 0 ) { nMin = nRangeMin; nMax = nRangeMax;
// make sure they're in the correct order
if ( nMin > nMax ) { int nTemp = nMin; nMin = nMax; nMax = nTemp; } }
if ( nMin > 0 ) { m_FontAmalgams[font].AddFont( pExtendedFont, 0x0000, nMin - 1 ); }
m_FontAmalgams[font].AddFont( winFont, nMin, nMax );
if ( nMax < 0xFFFF ) { m_FontAmalgams[font].AddFont( pExtendedFont, nMax + 1, 0xFFFF ); }
return true; } else if ( pExtendedFont ) { // the normal font failed to create
// just use the extended font for the full range
m_FontAmalgams[font].AddFont( pExtendedFont, 0x0000, 0xFFFF ); return true; } } // no valid font has been created, so fallback to a different font and try again
} while ( NULL != ( windowsFontName = GetFallbackFontName( windowsFontName ) ) );
// nothing successfully created
return false; }
//-----------------------------------------------------------------------------
// Purpose: adds glyphs to a font created by CreateFont()
//-----------------------------------------------------------------------------
bool CFontManager::SetBitmapFontGlyphSet(HFont font, const char *windowsFontName, float scalex, float scaley, int flags) { if ( m_FontAmalgams[font].GetCount() > 0 ) { // clear any existing fonts
m_FontAmalgams[font].RemoveAll(); }
CBitmapFont *winFont = CreateOrFindBitmapFont( windowsFontName, scalex, scaley, flags ); if ( winFont ) { // bitmap fonts are only 0-255
m_FontAmalgams[font].AddFont( winFont, 0x0000, 0x00FF ); return true; }
// nothing successfully created
return false; }
//-----------------------------------------------------------------------------
// Purpose: Creates a new win32 font, or reuses one if possible
//-----------------------------------------------------------------------------
font_t *CFontManager::CreateOrFindWin32Font(const char *windowsFontName, int tall, int weight, int blur, int scanlines, int flags) { // see if we already have the win32 font
font_t *winFont = NULL; int i; for (i = 0; i < m_Win32Fonts.Count(); i++) { if (m_Win32Fonts[i]->IsEqualTo(windowsFontName, tall, weight, blur, scanlines, flags)) { winFont = m_Win32Fonts[i]; break; } }
// create the new win32font if we didn't find it
if (!winFont) { MEM_ALLOC_CREDIT();
i = m_Win32Fonts.AddToTail(); #ifdef LINUX
int memSize = 0; void *pchFontData = pFontDataHelper( windowsFontName, memSize ); if ( pchFontData ) { m_Win32Fonts[i] = new font_t(); if (m_Win32Fonts[i]->CreateFromMemory( windowsFontName, pchFontData, memSize, tall, weight, blur, scanlines, flags)) { // add to the list
winFont = m_Win32Fonts[i]; } else { // failed to create, remove
delete m_Win32Fonts[i]; m_Win32Fonts.Remove(i); return NULL; } } else { #endif
m_Win32Fonts[i] = new font_t(); if (m_Win32Fonts[i]->Create(windowsFontName, tall, weight, blur, scanlines, flags)) { // add to the list
winFont = m_Win32Fonts[i]; } else { // failed to create, remove
delete m_Win32Fonts[i]; m_Win32Fonts.Remove(i); return NULL; } #ifdef LINUX
} #endif
}
return winFont; }
//-----------------------------------------------------------------------------
// Purpose: Creates a new win32 font, or reuses one if possible
//-----------------------------------------------------------------------------
CBitmapFont *CFontManager::CreateOrFindBitmapFont(const char *windowsFontName, float scalex, float scaley, int flags) { // see if we already have the font
CBitmapFont *winFont = NULL; int i; for ( i = 0; i < m_Win32Fonts.Count(); i++ ) { font_t *font = m_Win32Fonts[i];
// Only looking for bitmap fonts
int testflags = font->GetFlags(); if ( !( testflags & FONTFLAG_BITMAP ) ) { continue; }
CBitmapFont *bitmapFont = reinterpret_cast< CBitmapFont* >( font ); if ( bitmapFont->IsEqualTo( windowsFontName, scalex, scaley, flags ) ) { winFont = bitmapFont; break; } }
// create the font if we didn't find it
if ( !winFont ) { MEM_ALLOC_CREDIT();
i = m_Win32Fonts.AddToTail();
CBitmapFont *bitmapFont = new CBitmapFont(); if ( bitmapFont->Create( windowsFontName, scalex, scaley, flags ) ) { // add to the list
m_Win32Fonts[i] = bitmapFont; winFont = bitmapFont; } else { // failed to create, remove
delete bitmapFont; m_Win32Fonts.Remove(i); return NULL; } }
return winFont; }
//-----------------------------------------------------------------------------
// Purpose: sets the scale of a bitmap font
//-----------------------------------------------------------------------------
void CFontManager::SetFontScale(vgui::HFont font, float sx, float sy) { m_FontAmalgams[font].SetFontScale( sx, sy ); }
const char *CFontManager::GetFontName( HFont font ) { // ignore the amalgam of disparate char ranges, assume the first font
return m_FontAmalgams[font].GetFontName( 0 ); }
//-----------------------------------------------------------------------------
// Purpose: gets the windows font for the particular font in the amalgam
//-----------------------------------------------------------------------------
font_t *CFontManager::GetFontForChar( vgui::HFont font, wchar_t wch ) { return m_FontAmalgams[font].GetFontForChar(wch); }
//-----------------------------------------------------------------------------
// Purpose: returns the abc widths of a single character
//-----------------------------------------------------------------------------
void CFontManager::GetCharABCwide(HFont font, int ch, int &a, int &b, int &c) { font_t *winFont = m_FontAmalgams[font].GetFontForChar(ch); if (winFont) { winFont->GetCharABCWidths(ch, a, b, c); } else { // no font for this range, just use the default width
a = c = 0; b = m_FontAmalgams[font].GetFontMaxWidth(); } }
//-----------------------------------------------------------------------------
// Purpose: returns the max height of a font
//-----------------------------------------------------------------------------
int CFontManager::GetFontTall(HFont font) { return m_FontAmalgams[font].GetFontHeight(); }
//-----------------------------------------------------------------------------
// Purpose: returns the ascent of a font
//-----------------------------------------------------------------------------
int CFontManager::GetFontAscent(HFont font, wchar_t wch) { font_t *winFont = m_FontAmalgams[font].GetFontForChar(wch); if ( winFont ) { return winFont->GetAscent(); } else { return 0; } }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CFontManager::IsFontAdditive(HFont font) { return ( m_FontAmalgams[font].GetFlags( 0 ) & FONTFLAG_ADDITIVE ) ? true : false; }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CFontManager::IsBitmapFont(HFont font) { // A FontAmalgam is either some number of non-bitmap fonts, or a single bitmap font - so this check is valid
return ( m_FontAmalgams[font].GetFlags( 0 ) & FONTFLAG_BITMAP ) ? true : false; }
//-----------------------------------------------------------------------------
// Purpose: returns the pixel width of a single character
//-----------------------------------------------------------------------------
int CFontManager::GetCharacterWidth(HFont font, int ch) { if ( !iswcntrl( ch ) ) { int a, b, c; GetCharABCwide(font, ch, a, b, c); return (a + b + c); } return 0; }
//-----------------------------------------------------------------------------
// Purpose: returns the area of a text string, including newlines
//-----------------------------------------------------------------------------
void CFontManager::GetTextSize(HFont font, const wchar_t *text, int &wide, int &tall) { wide = 0; tall = 0; if (!text) return;
// AV - Calling GetFontTall() for an amalgam with multiple fonts will return
// the font height of the first font only! We should be doing something like:
//
// tall = FontManager().GetFontForChar( font, text[0] )->GetHeight();
//
// but that's a little hacky since we're only looking at the first character!
// Same goes for the calls to GetFontTall() in the for loop below
tall = GetFontTall(font);
float xx = 0; char chBefore = 0; char chAfter = 0; for (int i = 0; ; i++) { wchar_t ch = text[i]; if (ch == 0) { break; }
chAfter = text[i+1];
if (ch == '\n') { // AV - See note above about calling this instead: tall += FontManager().GetFontForChar( font, text[0] )->GetHeight();
tall += GetFontTall(font);
xx=0; } else if (ch == '&') { // underscore character, so skip
} else { float flWide, flabcA, flabcC; GetKernedCharWidth( font, ch, chBefore, chAfter, flWide, flabcA, flabcC ); xx += flWide; if (xx > wide) { wide = ceil(xx); } } chBefore = ch; } }
// font validation functions
struct FallbackFont_t { const char *font; const char *fallbackFont; };
const char *g_szValidAsianFonts[] = { #ifdef WIN32
"Marlett", #else
"Helvetica", #endif
NULL };
// list of how fonts fallback
FallbackFont_t g_FallbackFonts[] = { { "Times New Roman", "Courier New" }, { "Courier New", "Courier" }, { "Verdana", "Arial" }, { "Trebuchet MS", "Arial" }, #ifdef WIN32
{ "Tahoma", NULL }, { NULL, "Tahoma" }, // every other font falls back to this
#else
{ "Tahoma", "Helvetica" }, { "Helvetica", NULL }, { NULL, "Helvetica" } // every other font falls back to this
#endif
};
//-----------------------------------------------------------------------------
// Purpose: returns true if the font is in the list of OK asian fonts
//-----------------------------------------------------------------------------
bool CFontManager::IsFontForeignLanguageCapable(const char *windowsFontName) { if ( IsX360() ) { return false; }
for (int i = 0; g_szValidAsianFonts[i] != NULL; i++) { if (!stricmp(g_szValidAsianFonts[i], windowsFontName)) return true; }
// typeface isn't supported by asian languages
return false; }
//-----------------------------------------------------------------------------
// Purpose: fallback fonts
//-----------------------------------------------------------------------------
const char *CFontManager::GetFallbackFontName(const char *windowsFontName) { int i; for ( i = 0; g_FallbackFonts[i].font != NULL; i++ ) { if (!stricmp(g_FallbackFonts[i].font, windowsFontName)) return g_FallbackFonts[i].fallbackFont; }
// the ultimate fallback
return g_FallbackFonts[i].fallbackFont; }
struct Win98ForeignFallbackFont_t { const char *language; const char *fallbackFont; };
// list of how fonts fallback
Win98ForeignFallbackFont_t g_Win98ForeignFallbackFonts[] = { { "russian", "system" }, { "japanese", "win98japanese" }, { "thai", "system" }, #ifdef WIN32
{ NULL, "Tahoma" }, // every other font falls back to this
#else
{ NULL, "Helvetica" }, // every other font falls back to this
#endif
};
//-----------------------------------------------------------------------------
// Purpose: specialized fonts
//-----------------------------------------------------------------------------
const char *CFontManager::GetForeignFallbackFontName() { #ifdef WIN32
if ( s_bSupportsUnicode ) { if ( IsX360() ) { return "arial unicode ms"; }
// tahoma has all the necessary characters for asian/russian languages for winXP/2K+
return "Tahoma"; } #endif
int i; for (i = 0; g_Win98ForeignFallbackFonts[i].language != NULL; i++) { if (!stricmp(g_Win98ForeignFallbackFonts[i].language, m_szLanguage)) return g_Win98ForeignFallbackFonts[i].fallbackFont; }
// the ultimate fallback
return g_Win98ForeignFallbackFonts[i].fallbackFont; }
#if defined( _X360 )
bool CFontManager::GetCachedXUIMetrics( const char *pFontName, int tall, int style, XUIFontMetrics *pFontMetrics, XUICharMetrics charMetrics[256] ) { // linear lookup is good enough
CUtlSymbol fontSymbol = pFontName; bool bFound = false; int i; for ( i = 0; i < m_XUIMetricCache.Count(); i++ ) { if ( m_XUIMetricCache[i].fontSymbol == fontSymbol && m_XUIMetricCache[i].tall == tall && m_XUIMetricCache[i].style == style ) { bFound = true; break; } } if ( !bFound ) { return false; }
// get from the cache
*pFontMetrics = m_XUIMetricCache[i].fontMetrics; V_memcpy( charMetrics, m_XUIMetricCache[i].charMetrics, 256 * sizeof( XUICharMetrics ) ); return true; } #endif
#if defined( _X360 )
void CFontManager::SetCachedXUIMetrics( const char *pFontName, int tall, int style, XUIFontMetrics *pFontMetrics, XUICharMetrics charMetrics[256] ) { MEM_ALLOC_CREDIT();
int i = m_XUIMetricCache.AddToTail();
m_XUIMetricCache[i].fontSymbol = pFontName; m_XUIMetricCache[i].tall = tall; m_XUIMetricCache[i].style = style; m_XUIMetricCache[i].fontMetrics = *pFontMetrics; V_memcpy( m_XUIMetricCache[i].charMetrics, charMetrics, 256 * sizeof( XUICharMetrics ) ); } #endif
void CFontManager::ClearTemporaryFontCache() { #if defined( _X360 )
COM_TimestampedLog( "ClearTemporaryFontCache(): Start" );
m_XUIMetricCache.Purge();
// many fonts are blindly precached by vgui and never used
// font will re-open if glyph is actually requested
for ( int i = 0; i < m_Win32Fonts.Count(); i++ ) { m_Win32Fonts[i]->CloseResource(); }
COM_TimestampedLog( "ClearTemporaryFontCache(): Finish" ); #endif
}
//-----------------------------------------------------------------------------
// Purpose: returns the max height of a font
//-----------------------------------------------------------------------------
bool CFontManager::GetFontUnderlined( HFont font ) { return m_FontAmalgams[font].GetUnderlined(); }
void CFontManager::GetKernedCharWidth( vgui::HFont font, wchar_t ch, wchar_t chBefore, wchar_t chAfter, float &wide, float &flabcA, float &flabcC ) { wide = 0.0f; flabcA = 0.0f; Assert( font != vgui::INVALID_FONT ); if ( font == vgui::INVALID_FONT ) return; font_t *pFont = m_FontAmalgams[font].GetFontForChar(ch); if ( !pFont ) { // no font for this range, just use the default width
flabcA = 0.0f; wide = m_FontAmalgams[font].GetFontMaxWidth(); return; } if ( m_FontAmalgams[font].GetFontForChar( chBefore ) != pFont ) chBefore = 0; if ( m_FontAmalgams[font].GetFontForChar( chAfter ) != pFont ) chAfter = 0; pFont->GetKernedCharWidth( ch, chBefore, chAfter, wide, flabcA, flabcC ); }
#ifdef DBGFLAG_VALIDATE
//-----------------------------------------------------------------------------
// Purpose: Ensure that all of our internal structures are consistent, and
// account for all memory that we've allocated.
// Input: validator - Our global validator object
// pchName - Our name (typically a member var in our container)
//-----------------------------------------------------------------------------
void CFontManager::Validate( CValidator &validator, char *pchName ) { validator.Push( "CFontManager", this, pchName );
ValidateObj( m_FontAmalgams ); for ( int iFont = 0; iFont < m_FontAmalgams.Count(); iFont++ ) { ValidateObj( m_FontAmalgams[iFont] ); }
ValidateObj( m_Win32Fonts ); for ( int iWin32Font = 0; iWin32Font < m_Win32Fonts.Count(); iWin32Font++ ) { ValidatePtr( m_Win32Fonts[ iWin32Font ] ); }
validator.Pop(); } #endif // DBGFLAG_VALIDATE
|