#include "global.h" #include "Font.h" #include "IniFile.h" #include "RageTextureManager.h" #include "RageUtil.h" #include "RageLog.h" #include "RageException.h" #include "FontManager.h" #include "GameState.h" #include "ThemeManager.h" #include "GameManager.h" #include "FontCharmaps.h" #include "FontCharAliases.h" /* Last private-use Unicode character: */ const wchar_t Font::DEFAULT_GLYPH = 0xF8FF; FontPage::FontPage() { m_pTexture = NULL; m_iDrawExtraPixelsLeft = m_iDrawExtraPixelsRight = 0; } void FontPage::Load( FontPageSettings cfg ) { m_sTexturePath = cfg.m_sTexturePath; // load texture RageTextureID ID( m_sTexturePath ); if( cfg.m_sTextureHints != "default" ) ID.AdditionalTextureHints = cfg.m_sTextureHints; else ID.AdditionalTextureHints = "16bpp"; m_pTexture = TEXTUREMAN->LoadTexture( ID ); ASSERT( m_pTexture != NULL ); // load character widths vector aiFrameWidths; int default_width = m_pTexture->GetSourceFrameWidth(); if( cfg.m_iDefaultWidth != -1 ) default_width = cfg.m_iDefaultWidth; // Assume each character is the width of the frame by default. for( int i=0; iGetNumFrames(); i++ ) { map::const_iterator it = cfg.m_mapGlyphWidths.find(i); if( it != cfg.m_mapGlyphWidths.end() ) aiFrameWidths.push_back( it->second ); else aiFrameWidths.push_back( default_width ); } if( cfg.m_iAddToAllWidths ) { for( int i=0; iGetNumFrames(); i++ ) aiFrameWidths[i] += cfg.m_iAddToAllWidths; } if( cfg.m_fScaleAllWidthsBy != 1 ) { for( int i=0; iGetNumFrames(); i++ ) aiFrameWidths[i] = int(roundf( aiFrameWidths[i] * cfg.m_fScaleAllWidthsBy )); } m_iCharToGlyphNo = cfg.CharToGlyphNo; m_iLineSpacing = cfg.m_iLineSpacing; if( m_iLineSpacing == -1 ) m_iLineSpacing = m_pTexture->GetSourceFrameHeight(); int iBaseline=0; /* If we don't have a top and/or baseline, assume we're centered in the * frame, and that LineSpacing is the total height. */ if( cfg.m_iBaseline == -1 ) { float center = m_pTexture->GetSourceFrameHeight()/2.0f; cfg.m_iBaseline = int( center + m_iLineSpacing/2 ); } if( cfg.m_iTop == -1 ) { float center = m_pTexture->GetSourceFrameHeight()/2.0f; cfg.m_iTop = int( center - m_iLineSpacing/2 ); } iBaseline = cfg.m_iBaseline; m_iHeight = iBaseline - cfg.m_iTop; m_iDrawExtraPixelsLeft = cfg.m_iDrawExtraPixelsLeft; m_iDrawExtraPixelsRight = cfg.m_iDrawExtraPixelsRight; /* Shift the character up so the top will be rendered at the baseline. */ m_fVshift = (float) -iBaseline; SetTextureCoords( aiFrameWidths, cfg.m_iAdvanceExtraPixels ); SetExtraPixels( cfg.m_iDrawExtraPixelsLeft, cfg.m_iDrawExtraPixelsRight ); // LOG->Trace("Font %s: height %i, baseline %i ( == top %i)", // m_sTexturePath.c_str(), height, baseline, baseline-height); } void FontPage::SetTextureCoords( const vector &widths, int iAdvanceExtraPixels ) { for(int i = 0; i < m_pTexture->GetNumFrames(); ++i) { glyph g; g.m_pPage = this; /* Make a copy of each texture rect, reducing each to the actual dimensions * of the character (most characters don't take a full block). */ g.m_TexRect = *m_pTexture->GetTextureCoordRect(i);; /* Set the width and height to the width and line spacing, respectively. */ g.m_fWidth = float( widths[i] ); g.m_fHeight = float(m_pTexture->GetSourceFrameHeight()); g.m_iHadvance = int(g.m_fWidth) + iAdvanceExtraPixels; /* Do the same thing with X. Do this by changing the actual rendered * m_TexRect, instead of shifting it, so we don't render more than we * need to. */ g.m_fHshift = 0; { int iSourcePixelsToChopOff = m_pTexture->GetSourceFrameWidth() - widths[i]; if( (iSourcePixelsToChopOff % 2) == 1 ) { /* We don't want to chop off an odd number of pixels, since that'll * put our texture coordinates between texels and make things blurrier. * Note that, since we set m_iHadvance above, this merely expands what * we render; it doesn't advance the cursor further. So, glyphs * that have an odd width should err to being a pixel offcenter left, * not right. */ --iSourcePixelsToChopOff; ++g.m_fWidth; } const float fTexCoordsToChopOff = iSourcePixelsToChopOff * m_pTexture->GetSourceToTexCoordsRatio(); g.m_TexRect.left += fTexCoordsToChopOff/2; g.m_TexRect.right -= fTexCoordsToChopOff/2; } g.m_pTexture = m_pTexture; m_aGlyphs.push_back(g); } } void FontPage::SetExtraPixels( int iDrawExtraPixelsLeft, int iDrawExtraPixelsRight ) { /* Hack: do one more than we were asked to; I think a lot of fonts are one * too low. */ iDrawExtraPixelsRight++; iDrawExtraPixelsLeft++; if( (iDrawExtraPixelsLeft % 2) == 1 ) ++iDrawExtraPixelsLeft; /* Adjust for iDrawExtraPixelsLeft and iDrawExtraPixelsRight. */ for( unsigned i = 0; i < m_aGlyphs.size(); ++i ) { int iFrameWidth = m_pTexture->GetSourceFrameWidth(); float fCharWidth = m_aGlyphs[i].m_fWidth; /* Extra pixels to draw to the left and right. We don't have to * worry about alignment here; fCharWidth is always even (by * SetTextureCoords) and iFrameWidth are almost always even. */ float fExtraLeft = min( float(iDrawExtraPixelsLeft), (iFrameWidth-fCharWidth)/2.0f ); float fExtraRight = min( float(iDrawExtraPixelsRight), (iFrameWidth-fCharWidth)/2.0f ); /* Move left and expand right. */ m_aGlyphs[i].m_TexRect.left -= fExtraLeft * m_pTexture->GetSourceToTexCoordsRatio(); m_aGlyphs[i].m_TexRect.right += fExtraRight * m_pTexture->GetSourceToTexCoordsRatio(); m_aGlyphs[i].m_fHshift -= fExtraLeft; m_aGlyphs[i].m_fWidth += fExtraLeft + fExtraRight; } } FontPage::~FontPage() { if( m_pTexture != NULL ) TEXTUREMAN->UnloadTexture( m_pTexture ); } int Font::GetLineWidthInSourcePixels( const wstring &szLine ) const { int iLineWidth = 0; for( unsigned i=0; i 0 ) { /* Add overdraw. */ iLineWidth += GetGlyph(szLine[0]).m_pPage->m_iDrawExtraPixelsLeft; iLineWidth += GetGlyph(szLine[szLine.size()-1]).m_pPage->m_iDrawExtraPixelsRight; } return iLineWidth; } int Font::GetLineHeightInSourcePixels( const wstring &szLine ) const { int iLineHeight = 0; /* The height of a line is the height of its tallest used font page. */ for( unsigned i=0; im_iHeight ); return iLineHeight; } Font::Font() { //LOG->Trace( "Font::LoadFromFontName(%s)", sASCIITexturePath.c_str() ); m_iRefCount = 1; m_pDefault = NULL; } Font::~Font() { Unload(); } void Font::Unload() { for( unsigned i = 0; i < m_apPages.size(); ++i ) delete m_apPages[i]; m_apPages.clear(); m_iCharToGlyph.clear(); m_pDefault = NULL; /* Don't clear the refcount. We've unloaded, but that doesn't mean things * aren't still pointing to us. */ } void Font::Reload() { Unload(); ASSERT( !path.empty() ); Load( path, m_sChars ); } void Font::AddPage( FontPage *m_pPage ) { m_apPages.push_back( m_pPage ); for( map::const_iterator it = m_pPage->m_iCharToGlyphNo.begin(); it != m_pPage->m_iCharToGlyphNo.end(); ++it ) { m_iCharToGlyph[it->first] = &m_pPage->m_aGlyphs[it->second]; } } void Font::MergeFont(Font &f) { /* If we don't have a font page yet, and f does, grab the default font * page. It'll usually be overridden later on by one of our own font * pages; this will be used only if we don't have any font pages at * all. */ if( m_pDefault == NULL ) m_pDefault = f.m_pDefault; for(map::iterator it = f.m_iCharToGlyph.begin(); it != f.m_iCharToGlyph.end(); ++it) { m_iCharToGlyph[it->first] = it->second; } m_apPages.insert( m_apPages.end(), f.m_apPages.begin(), f.m_apPages.end() ); f.m_apPages.clear(); } const glyph &Font::GetGlyph( wchar_t c ) const { ASSERT(c >= 0 && c <= 0xFFFFFF); /* Fast path: */ if( c < (int) ARRAYSIZE(m_iCharToGlyphCache) && m_iCharToGlyphCache[c] ) return *m_iCharToGlyphCache[c]; /* Try the regular character. */ map::const_iterator it = m_iCharToGlyph.find(c); /* If that's missing, use the default glyph. */ if(it == m_iCharToGlyph.end()) it = m_iCharToGlyph.find(DEFAULT_GLYPH); if(it == m_iCharToGlyph.end()) RageException::Throw( "The default glyph is missing from the font '%s'", path.c_str() ); return *it->second; } bool Font::FontCompleteForString( const wstring &str ) const { map::const_iterator m_pDefault = m_iCharToGlyph.find( DEFAULT_GLYPH ); if( m_pDefault == m_iCharToGlyph.end() ) RageException::Throw( "The default glyph is missing from the font '%s'", path.c_str() ); for( unsigned i = 0; i < str.size(); ++i ) { /* If the glyph for this character is the default glyph, we're incomplete. */ const glyph &g = GetGlyph( str[i] ); if( &g == m_pDefault->second ) return false; } return true; } void Font::CapsOnly() { /* For each uppercase character that we have a mapping for, add * a lowercase one. */ for(char c = 'A'; c <= 'Z'; ++c) { map::const_iterator it = m_iCharToGlyph.find(c); if(it == m_iCharToGlyph.end()) continue; m_iCharToGlyph[(char) tolower(c)] = it->second; } } void Font::SetDefaultGlyph( FontPage *pPage ) { ASSERT( pPage ); ASSERT( !pPage->m_aGlyphs.empty() ); m_pDefault = pPage; } CString Font::GetFontName( CString sFileName ) { CString sOrig = sFileName; CString sDir, sFName, sExt; splitpath( sFileName, sDir, sFName, sExt ); sFileName = sFName; /* If it ends in an extension, remove it. */ static Regex drop_ext( "\\....$" ); if( drop_ext.Compare(sFileName) ) sFileName.erase( sFileName.size()-4 ); /* If it ends in a resolution spec, remove it. */ CStringArray asMatch; static Regex ResSpec( "( \\(res [0-9]+x[0-9]+\\))$" ); if( ResSpec.Compare(sFileName, asMatch) ) sFileName.erase(sFileName.size()-asMatch[0].size()); /* If it ends in a dimension spec, remove it. */ static Regex DimSpec( "( [0-9]+x[0-9]+)$" ); if( DimSpec.Compare(sFileName, asMatch) ) sFileName.erase( sFileName.size()-asMatch[0].size() ); /* If it ends in texture hints, remove them. */ static Regex Hints( "( \\([^\\)]+\\))$" ); if( Hints.Compare(sFileName, asMatch) ) sFileName.erase( sFileName.size()-asMatch[0].size() ); /* If it ends in a page name, remove it. */ static Regex PageName("( \\[.+\\])$"); if( PageName.Compare( sFileName, asMatch ) ) sFileName.erase( sFileName.size()-asMatch[0].size() ); TrimRight( sFileName ); if( sFileName.empty() ) RageException::Throw( "Can't parse font filename \"%s\"", sOrig.c_str() ); sFileName.MakeLower(); return sFileName; } void Font::WeedFontNames( vector &v, const CString &sFileName ) { CString sFontName = Font::GetFontName( sFileName ); /* Weed out false matches. (For example, this gets rid of "normal2" when * we're really looking for "normal".) */ for( unsigned i = 0; i < v.size(); ) { if( sFontName.CompareNoCase(Font::GetFontName(v[i])) ) v.erase( v.begin()+i ); else ++i; } } /* Given a file in a font, find all of the files for the font. * * Possibilities: * * Normal 16x16.png * Normal [other] 16x16.png * Normal [more] 8x8.png * Normal 16x16.ini * Normal.ini * * Any of the above should find all of the above. Allow the * extension to be omitted. */ void Font::GetFontPaths( const CString &sFontOrTextureFilePath, CStringArray &asTexturePathsOut, CString &sIniPath ) { CString sDir, sFName, sExt; splitpath( sFontOrTextureFilePath, sDir, sFName, sExt ); /* Don't give us a redir; resolve those before sending them here. */ ASSERT( sExt.CompareNoCase("redir") ); /* sFName can't be empty, or we don't know what to search for. */ ASSERT( !sFName.empty() ); CString sFontName = GetFontName( sFName ); CStringArray asFiles; GetDirListing( sDir+sFontName + "*", asFiles, false, false ); for( unsigned i = 0; i < asFiles.size(); ++i ) { /* We now have a list of possibilities, but it may include false positives, * such as "Normal2" when the font name is "Normal". Weed them. */ if( GetFontName(asFiles[i]).CompareNoCase(sFontName) ) continue; /* If it's an INI, and we don't already have an INI, use it. */ if( !asFiles[i].Right(4).CompareNoCase(".ini")) { if( !sIniPath.empty() ) RageException::Throw( "More than one INI found\n%s\n%s", sIniPath.c_str(), asFiles[i].c_str() ); sIniPath = sDir+asFiles[i]; continue; } asTexturePathsOut.push_back( sDir+asFiles[i] ); } } CString Font::GetPageNameFromFileName( const CString &sFilename ) { size_t begin = sFilename.find_first_of( '[' ); if( begin == string::npos ) return "main"; size_t end = sFilename.find_first_of( ']', begin ); if( end == string::npos ) return "main"; begin++; end--; if( end == begin ) return "main"; return sFilename.substr( begin, end-begin+1 ); } void Font::LoadFontPageSettings( FontPageSettings &cfg, IniFile &ini, const CString &sTexturePath, const CString &sPageName, CString sChars ) { cfg.m_sTexturePath = sTexturePath; /* If we have any characters to map, add them. */ for( unsigned n=0; nTrace("Loading font page '%s' settings from page name '%s'", // TexturePath.c_str(), sPageName.c_str()); ini.GetValue( sPageName, "DrawExtraPixelsLeft", cfg.m_iDrawExtraPixelsLeft ); ini.GetValue( sPageName, "DrawExtraPixelsRight", cfg.m_iDrawExtraPixelsRight ); ini.GetValue( sPageName, "AddToAllWidths", cfg.m_iAddToAllWidths ); ini.GetValue( sPageName, "ScaleAllWidthsBy", cfg.m_fScaleAllWidthsBy ); ini.GetValue( sPageName, "LineSpacing", cfg.m_iLineSpacing ); ini.GetValue( sPageName, "Top", cfg.m_iTop ); ini.GetValue( sPageName, "Baseline", cfg.m_iBaseline ); ini.GetValue( sPageName, "DefaultWidth", cfg.m_iDefaultWidth ); ini.GetValue( sPageName, "AdvanceExtraPixels", cfg.m_iAdvanceExtraPixels ); ini.GetValue( sPageName, "TextureHints", cfg.m_sTextureHints ); /* Iterate over all keys. */ const XNode* pNode = ini.GetChild( sPageName ); if( pNode ) { FOREACH_CONST_Attr( pNode, pAttr ) { CString sName = pAttr->m_sName; const CString &sValue = pAttr->m_sValue; sName.MakeUpper(); /* If val is an integer, it's a width, eg. "10=27". */ if( IsAnInt(sName) ) { cfg.m_mapGlyphWidths[atoi(sName)] = atoi( sValue ); continue; } /* "map codepoint=frame" maps a char to a frame. */ if( sName.substr(0, 4) == "MAP " ) { /* * map CODEPOINT=frame. CODEPOINT can be * 1. U+hexval * 2. an alias ("oq") * 3. a game type followed by a game alias, eg "pump menuleft" * 4. a character in quotes ("X") * * map 1=2 is the same as * range unicode #1-1=2 */ CString sCodepoint = sName.substr(4); /* "CODEPOINT" */ const Game* pGame = NULL; if( sCodepoint.find_first_of(' ') != sCodepoint.npos ) { /* There's a space; the first word should be a game type. Split it. */ unsigned pos = sCodepoint.find_first_of( ' ' ); CString gamename = sCodepoint.substr( 0, pos ); sCodepoint = sCodepoint.substr( pos+1 ); pGame = GameManager::StringToGameType(gamename); if( pGame == NULL ) { LOG->Warn( "Font definition '%s' uses unknown game type '%s'", ini.GetPath().c_str(), gamename.c_str() ); continue; } } wchar_t c; if( sCodepoint.substr(0, 2) == "U+" && IsHexVal(sCodepoint.substr(2)) ) sscanf( sCodepoint.substr(2).c_str(), "%x", &c ); else if( sCodepoint.size() > 0 && utf8_get_char_len(sCodepoint[0]) == int(sCodepoint.size()) ) { c = utf8_get_char( sCodepoint.c_str() ); if(c == wchar_t(-1)) LOG->Warn("Font definition '%s' has an invalid value '%s'.", ini.GetPath().c_str(), sName.c_str() ); } else if( !FontCharAliases::GetChar(sCodepoint, c) ) { LOG->Warn("Font definition '%s' has an invalid value '%s'.", ini.GetPath().c_str(), sName.c_str() ); continue; } cfg.CharToGlyphNo[c] = atoi( sValue ); continue; } if( sName.substr(0, 6) == "RANGE " ) { /* * range CODESET=first_frame or * range CODESET #start-end=first_frame * eg * range CP1252=0 (default for 256-frame fonts) * range ASCII=0 (default for 128-frame fonts) * * (Start and end are in hex.) * * Map two high-bit portions of ISO-8859- to one font: * range ISO-8859-2 #80-FF=0 * range ISO-8859-3 #80-FF=128 * * Map hiragana to 0-84: * range Unicode #3041-3094=0 */ vector asMatches; static Regex parse("^RANGE ([A-Z\\-]+)( ?#([0-9A-F]+)-([0-9A-F]+))?$"); bool bMatch = parse.Compare( sName, asMatches ); ASSERT( asMatches.size() == 4 ); /* 4 parens */ if( !bMatch || asMatches[0].empty() ) RageException::Throw("Font definition '%s' has an invalid range '%s': parse error", ini.GetPath().c_str(), sName.c_str() ); /* We must have either 1 match (just the codeset) or 4 (the whole thing). */ int iCount = -1; int iFirst = 0; if( !asMatches[2].empty() ) { sscanf( asMatches[2].c_str(), "%x", &iFirst ); int iLast; sscanf( asMatches[3].c_str(), "%x", &iLast ); if( iLast < iFirst ) RageException::Throw("Font definition '%s' has an invalid range '%s': %i < %i.", ini.GetPath().c_str(), sName.c_str(), iLast < iFirst ); iCount = iLast - iFirst + 1; } CString sRet = cfg.MapRange( asMatches[0], iFirst, atoi(sValue), iCount ); if( !sRet.empty() ) RageException::Throw( "Font definition '%s' has an invalid range '%s': %s.", ini.GetPath().c_str(), sName.c_str(), sRet.c_str() ); continue; } if( sName.substr(0, 5) == "LINE " ) { /* line ROW=CHAR1CHAR2CHAR3CHAR4 * eg. * line 0=ABCDEFGH * * This lets us assign characters very compactly and readably. */ CString sRowStr = sName.substr(5); ASSERT( IsAnInt(sRowStr) ); const int iRow = atoi( sRowStr.c_str() ); const int iFirstFrame = iRow * iNumFramesWide; if( iRow > iNumFramesHigh ) RageException::Throw( "The font definition \"%s\" tries to assign line %i, but the font is only %i characters high", ini.GetPath().c_str(), iFirstFrame, iNumFramesHigh ); /* Decode the string. */ const wstring wdata( CStringToWstring(sValue) ); if( int(wdata.size()) > iNumFramesWide ) RageException::Throw( "The font definition \"%s\" assigns %i characters to row %i (\"%ls\"), but the font only has %i characters wide", ini.GetPath().c_str(), wdata.size(), iRow, wdata.c_str(), iNumFramesWide ); for( unsigned i = 0; i < wdata.size(); ++i ) cfg.CharToGlyphNo[wdata[i]] = iFirstFrame+i; } } } /* If it's 128 or 256 frames, default to ASCII or CP1252, * respectively. If it's anything else, we don't know what it * is, so don't make any default mappings (the INI needs to do * it itself). */ if( sPageName != "common" && cfg.CharToGlyphNo.empty() ) { if( iNumFrames == 128 ) cfg.MapRange( "ascii", 0, 0, -1 ); else if( iNumFrames == 256 ) cfg.MapRange( "cp1252", 0, 0, -1 ); else if( iNumFrames == 15 ) cfg.MapRange( "numbers", 0, 0, -1 ); else LOG->Trace( "Font page \"%s\" has no characters", sTexturePath.c_str() ); } /* If ' ' is set and nbsp is not, set nbsp. */ if( cfg.CharToGlyphNo.find(' ') != cfg.CharToGlyphNo.end() ) cfg.CharToGlyphNo[0x00A0] = cfg.CharToGlyphNo[' ']; } CString FontPageSettings::MapRange( CString sMapping, int iMapOffset, int iGlyphNo, int iCount ) { if( !sMapping.CompareNoCase("Unicode") ) { /* Special case. */ if( iCount == -1 ) return "Can't map all of Unicode to one font page"; /* don't do that */ /* What's a practical limit? A 2048x2048 texture could contain 16x16 characters, * which is 16384 glyphs. (Use a grayscale map and that's only 4 megs.) Let's use * that as a cap. (We don't want to go crazy if someone says "range Unicode * #0-FFFFFFFF".) */ if( iCount > 16384 ) return ssprintf( "Can't map %i glyphs to one font page", iCount ); while( iCount ) { CharToGlyphNo[iMapOffset] = iGlyphNo; iMapOffset++; iGlyphNo++; iCount--; } return ""; } const wchar_t *pMapping = FontCharmaps::get_char_map( sMapping ); if( pMapping == NULL ) return "Unknown mapping"; while( *pMapping != 0 && iMapOffset ) { pMapping++; --iMapOffset; } if( iMapOffset ) return "Map overflow"; /* there aren't enough characters in the map */ /* If iCount is -1, set it to the number of characters in the map. */ if( iCount == -1 ) for( iCount = 0; pMapping[iCount] != 0; ++iCount ) ; while( *pMapping != 0 ) { if( *pMapping != FontCharmaps::M_SKIP ) CharToGlyphNo[*pMapping] = iGlyphNo; pMapping++; iGlyphNo++; iCount--; } if( iCount ) return "Map overflow"; /* there aren't enough characters in the map */ return ""; } static CStringArray LoadStack; /* * A font set is a set of files, eg: * * Normal 16x16.png * Normal [other] 16x16.png * Normal [more] 8x8.png * Normal 16x16.ini (the 16x16 here is optional) * * Only one texture is required; the INI is optional. [1] This is * designed to be backwards-compatible. * * sFontOrTextureFilePath can be a partial path, eg. * "Themes/default/Fonts/Normal" * or a complete path to a texture file (in which case no other * files will be searched for). * * The entire font can be redirected; that's handled in ThemeManager. * Individual font files can not be redirected. * * TODO: * [main] * import=FontName,FontName2 (load other fonts) * * [1] If a file has no INI and sChars is not set, it will receive a default * mapping of ASCII or ISO-8859-1 if the font has exactly 128 or 256 frames. * However, if it doesn't, we don't know what it is and the font will receive * no default mapping. A font isn't useful with no characters mapped. */ void Font::Load(const CString &sFontOrTextureFilePath, CString sChars) { /* Check for recursion (recursive imports). */ { for(unsigned i = 0; i < LoadStack.size(); ++i) { if(LoadStack[i] == sFontOrTextureFilePath) { CString str = join("\n", LoadStack); str += "\n" + sFontOrTextureFilePath; RageException::Throw("Font import recursion detected\n%s", str.c_str()); } } LoadStack.push_back(sFontOrTextureFilePath); } /* The font is not already loaded. Figure out what we have. */ CHECKPOINT_M( ssprintf("Font::Load(\"%s\",\"%s\").", sFontOrTextureFilePath.c_str(), m_sChars.c_str()) ); path = sFontOrTextureFilePath; m_sChars = sChars; /* Get the filenames associated with this font. */ CStringArray TexturePaths; CString sIniPath; GetFontPaths( sFontOrTextureFilePath, TexturePaths, sIniPath ); /* If we don't have at least one INI or at least one texture path, * we have nothing at all. */ ASSERT( !sIniPath.empty() || TexturePaths.size() ); bool bCapitalsOnly = false; /* If we have an INI, load it. */ IniFile ini; if( !sIniPath.empty() ) { ini.ReadFile( sIniPath ); ini.RenameKey("Char Widths", "main"); ini.GetValue( "main", "CapitalsOnly", bCapitalsOnly ); } { /* If this is a top-level font (not a subfont), load the default font first. */ CStringArray ImportList; if( LoadStack.size() == 1 ) ImportList.push_back("Common default"); /* Check to see if we need to import any other fonts. Do this * before loading this font, so any characters in this font * override imported characters. */ CString imports; ini.GetValue( "main", "import", imports ); split(imports, ",", ImportList, true); for(unsigned i = 0; i < ImportList.size(); ++i) { CString path = THEME->GetPathF( "", ImportList[i], true ); if( path == "" ) { LOG->Warn("Font \"%s\" imports a font \"%s\" that doesn't exist", sFontOrTextureFilePath.c_str(), ImportList[i].c_str()); continue; } Font subfont; subfont.Load(path, ""); MergeFont(subfont); } } /* Load each font page. */ for(unsigned i = 0; i < TexturePaths.size(); ++i) { const CString sTexturePath = TexturePaths[i]; FontPage *pPage = new FontPage; /* Grab the page name, eg "foo" from "Normal [foo].png". */ CString sPagename = GetPageNameFromFileName( sTexturePath ); /* Load settings for this page from the INI. */ FontPageSettings cfg; LoadFontPageSettings( cfg, ini, TexturePaths[i], "common", sChars ); LoadFontPageSettings( cfg, ini, TexturePaths[i], sPagename, sChars ); /* Go. */ pPage->Load( cfg ); /* Expect at least as many frames as we have premapped characters. */ /* Make sure that we don't map characters to frames we don't actually * have. This can happen if the font is too small for an sChars. */ for( map::const_iterator it = pPage->m_iCharToGlyphNo.begin(); it != pPage->m_iCharToGlyphNo.end(); ++it ) { if( it->second < pPage->m_pTexture->GetNumFrames() ) continue; /* OK */ CString sError = ssprintf( "The font '%s' maps %s to frame %i, but the font only has %i frames.", TexturePaths[i].c_str(), WcharDisplayText(wchar_t(it->first)).c_str(), it->second, pPage->m_pTexture->GetNumFrames() ); RageException::Throw( sError ); } // LOG->Trace( "Adding page %s (%s) to %s; %i glyphs", // TexturePaths[i].c_str(), sPagename.c_str(), // sFontOrTextureFilePath.c_str(), pPage->m_iCharToGlyphNo.size() ); AddPage( pPage ); /* If this is the first font loaded, or it's called "main", this page's * properties become the font's properties. */ if( i == 0 || sPagename == "main" ) SetDefaultGlyph( pPage ); } if( bCapitalsOnly ) CapsOnly(); if( m_iCharToGlyph.empty() ) LOG->Warn("Font %s has no characters", sFontOrTextureFilePath.c_str()); LoadStack.pop_back(); if( LoadStack.empty() ) { /* Cache ASCII glyphs. */ ZERO( m_iCharToGlyphCache ); map::iterator it; for( it = m_iCharToGlyph.begin(); it != m_iCharToGlyph.end(); ++it ) if( it->first < (int) ARRAYSIZE(m_iCharToGlyphCache) ) m_iCharToGlyphCache[it->first] = it->second; } } /* * (c) 2001-2004 Glenn Maynard, Chris Danford * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons to * whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */