#include "global.h" #include "Font.h" #include "IniFile.h" #include "RageTextureManager.h" #include "RageUtil.h" #include "RageLog.h" #include "FontManager.h" #include "ThemeManager.h" #include "FontCharmaps.h" #include "FontCharAliases.h" #include "arch/Dialog/Dialog.h" FontPage::FontPage(): m_iHeight(0), m_iLineSpacing(0), m_fVshift(0), m_iDrawExtraPixelsLeft(0), m_iDrawExtraPixelsRight(0), m_FontPageTextures(), m_sTexturePath(""), m_aGlyphs(), m_iCharToGlyphNo() {} void FontPage::Load( const FontPageSettings &cfg ) { m_sTexturePath = cfg.m_sTexturePath; // load texture RageTextureID ID1( m_sTexturePath ); if( cfg.m_sTextureHints != "default" ) ID1.AdditionalTextureHints = cfg.m_sTextureHints; m_FontPageTextures.m_pTextureMain = TEXTUREMAN->LoadTexture( ID1 ); ASSERT( m_FontPageTextures.m_pTextureMain != NULL ); RageTextureID ID2 = ID1; // "arial 20 16x16 [main].png" => "arial 20 16x16 [main-stroke].png" if( ID2.filename.find("]") != string::npos ) { ID2.filename.Replace( "]", "-stroke]" ); if( IsAFile(ID2.filename) ) { m_FontPageTextures.m_pTextureStroke = TEXTUREMAN->LoadTexture( ID2 ); ASSERT( m_FontPageTextures.m_pTextureStroke != NULL ); ASSERT_M( m_FontPageTextures.m_pTextureMain->GetSourceFrameWidth() == m_FontPageTextures.m_pTextureStroke->GetSourceFrameWidth(), ssprintf("'%s' and '%s' must have the same frame widths", ID1.filename.c_str(), ID2.filename.c_str()) ); ASSERT_M( m_FontPageTextures.m_pTextureMain->GetNumFrames() == m_FontPageTextures.m_pTextureStroke->GetNumFrames(), ssprintf("'%s' and '%s' must have the same frame dimensions", ID1.filename.c_str(), ID2.filename.c_str()) ); } } // load character widths vector aiFrameWidths; int default_width = m_FontPageTextures.m_pTextureMain->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] = lrintf( aiFrameWidths[i] * cfg.m_fScaleAllWidthsBy ); } m_iCharToGlyphNo = cfg.CharToGlyphNo; m_iLineSpacing = cfg.m_iLineSpacing; if( m_iLineSpacing == -1 ) m_iLineSpacing = m_FontPageTextures.m_pTextureMain->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. */ iBaseline = cfg.m_iBaseline; if( iBaseline == -1 ) { float center = m_FontPageTextures.m_pTextureMain->GetSourceFrameHeight()/2.0f; iBaseline = int( center + m_iLineSpacing/2 ); } int iTop = cfg.m_iTop; if( iTop == -1 ) { float center = m_FontPageTextures.m_pTextureMain->GetSourceFrameHeight()/2.0f; iTop = int( center - m_iLineSpacing/2 ); } m_iHeight = iBaseline - 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_FontPageTextures.m_pTextureMain->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_FontPageTextures.m_pTextureMain->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_FontPageTextures.m_pTextureMain->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_FontPageTextures.m_pTextureMain->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_FontPageTextures.m_pTextureMain->GetSourceToTexCoordsRatioX(); g.m_TexRect.left += fTexCoordsToChopOff/2; g.m_TexRect.right -= fTexCoordsToChopOff/2; } g.m_FontPageTextures = m_FontPageTextures; m_aGlyphs.push_back(g); } } void FontPage::SetExtraPixels( int iDrawExtraPixelsLeft, int iDrawExtraPixelsRight ) { // Most fonts don't take the stroke into account, so if it shows up, it'll // be cut off. I now understand why this code was here before. -freem iDrawExtraPixelsRight++; iDrawExtraPixelsLeft++; if( (iDrawExtraPixelsLeft % 2) == 1 ) ++iDrawExtraPixelsLeft; // Adjust for iDrawExtraPixelsLeft and iDrawExtraPixelsRight. for( unsigned i = 0; i < m_aGlyphs.size(); ++i ) { int iFrameWidth = m_FontPageTextures.m_pTextureMain->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_FontPageTextures.m_pTextureMain->GetSourceToTexCoordsRatioX(); m_aGlyphs[i].m_TexRect.right += fExtraRight * m_FontPageTextures.m_pTextureMain->GetSourceToTexCoordsRatioX(); m_aGlyphs[i].m_fHshift -= fExtraLeft; m_aGlyphs[i].m_fWidth += fExtraLeft + fExtraRight; } } FontPage::~FontPage() { if( m_FontPageTextures.m_pTextureMain != NULL ) { TEXTUREMAN->UnloadTexture( m_FontPageTextures.m_pTextureMain ); m_FontPageTextures.m_pTextureMain = NULL; } if( m_FontPageTextures.m_pTextureStroke != NULL ) { TEXTUREMAN->UnloadTexture( m_FontPageTextures.m_pTextureStroke ); m_FontPageTextures.m_pTextureStroke = NULL; } } int Font::GetLineWidthInSourcePixels( const wstring &szLine ) const { int iLineWidth = 0; for( unsigned i=0; im_iHeight ); return iLineHeight; } Font::Font(): m_iRefCount(1), path(""), m_apPages(), m_pDefault(NULL), m_iCharToGlyph(), m_bRightToLeft(false), // strokes aren't shown by default, hence the Color. m_DefaultStrokeColor(RageColor(0,0,0,0)), m_sChars("") {} Font::~Font() { Unload(); } void Font::Unload() { //LOG->Trace("Font:Unload '%s'",path.c_str()); 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) ARRAYLEN(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(FONT_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 mapDefault = m_iCharToGlyph.find( FONT_DEFAULT_GLYPH ); if( mapDefault == 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 == mapDefault->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; } // Given the INI for a font, find all of the texture pages for the font. void Font::GetFontPaths( const RString &sFontIniPath, vector &asTexturePathsOut ) { RString sPrefix = SetExtension( sFontIniPath, "" ); vector asFiles; GetDirListing( sPrefix + "*", asFiles, false, true ); for( unsigned i = 0; i < asFiles.size(); ++i ) { if( !asFiles[i].Right(4).EqualsNoCase(".ini") ) asTexturePathsOut.push_back( asFiles[i] ); } } RString Font::GetPageNameFromFileName( const RString &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 RString &sTexturePath, const RString &sPageName, RString 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 ) { RString sName = pAttr->first; const XNodeValue *pValue = pAttr->second; sName.MakeUpper(); // If val is an integer, it's a width, eg. "10=27". if( IsAnInt(sName) ) { cfg.m_mapGlyphWidths[StringToInt(sName)] = pValue->GetValue(); 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 character in quotes ("X") * * map 1=2 is the same as * range unicode #1-1=2 */ RString sCodepoint = sName.substr(4); // "CODEPOINT" 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] = pValue->GetValue(); 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-Z0-9\\-]+)( ?#([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; } RString sRet = cfg.MapRange( asMatches[0], iFirst, pValue->GetValue(), 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. */ RString sRowStr = sName.substr(5); TrimLeft( sRowStr ); ASSERT( IsAnInt(sRowStr) ); const int iRow = StringToInt( sRowStr ); 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( RStringToWstring(pValue->GetValue()) ); if( int(wdata.size()) > iNumFramesWide ) RageException::Throw( "The font definition \"%s\" assigns %i characters to row %i (\"%ls\"), but the font is only %i characters wide.", ini.GetPath().c_str(), (int)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. 5x3 and 4x4 numbers fonts are supported as well. * If it's anything else, we don't know what it is, so don't make * any default mappings (the INI needs to do that itself). */ if( sPageName != "common" && cfg.CharToGlyphNo.empty() ) { switch( iNumFrames ) { case 128: cfg.MapRange( "ascii", 0, 0, -1 ); break; case 256: cfg.MapRange( "cp1252", 0, 0, -1 ); break; case 15: case 16: cfg.MapRange( "numbers", 0, 0, -1 ); break; default: 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[' ']; } RString FontPageSettings::MapRange( RString 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 RString(); } 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 RString(); } static vector 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) * * One INI and at least one texture is required. * * The entire font can be redirected; that's handled in ThemeManager. * Individual font files can not be redirected. * * If a file has no characters 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. * 5x3 (15 frames) and 4x4 (16 frames) numbers sheets are also supported. * 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 RString &sIniPath, RString sChars ) { ASSERT_M( !GetExtension(sIniPath).CompareNoCase("ini"), sIniPath ); //LOG->Trace( "Font: Loading new font '%s'",sIniPath.c_str()); // Check for recursion (recursive imports). for( unsigned i = 0; i < LoadStack.size(); ++i ) { if( LoadStack[i] == sIniPath ) { RString str = join("\n", LoadStack); str += "\n" + sIniPath; RageException::Throw( "Font import recursion detected\n%s", str.c_str() ); } } LoadStack.push_back( sIniPath ); // The font is not already loaded. Figure out what we have. CHECKPOINT_M( ssprintf("Font::Load(\"%s\",\"%s\").", sIniPath.c_str(), m_sChars.c_str()) ); path = sIniPath; m_sChars = sChars; // Get the filenames associated with this font. vector asTexturePaths; GetFontPaths( sIniPath, asTexturePaths ); bool bCapitalsOnly = false; // If we have an INI, load it. IniFile ini; if( !sIniPath.empty() ) { ini.ReadFile( sIniPath ); ini.RenameKey("Char Widths", "main"); // backward compat ini.GetValue( "common", "CapitalsOnly", bCapitalsOnly ); ini.GetValue( "common", "RightToLeft", m_bRightToLeft ); RString s; if( ini.GetValue( "common", "DefaultStrokeColor", s ) ) m_DefaultStrokeColor.FromString( s ); } { vector ImportList; bool bIsTopLevelFont = LoadStack.size() == 1; // If this is a top-level font (not a subfont), load the default font first. if( bIsTopLevelFont ) 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. */ RString imports; ini.GetValue( "main", "import", imports ); split(imports, ",", ImportList, true); if( bIsTopLevelFont && imports.empty() && asTexturePaths.empty() ) { RString s = ssprintf( "Font \"%s\" is a top-level font with no textures or imports.", sIniPath.c_str() ); Dialog::OK( s ); } for(unsigned i = 0; i < ImportList.size(); ++i) { RString sPath = THEME->GetPathF( "", ImportList[i], true ); if( sPath == "" ) { RString s = ssprintf( "Font \"%s\" imports a font \"%s\" that doesn't exist", sIniPath.c_str(), ImportList[i].c_str() ); Dialog::OK( s ); continue; } Font subfont; subfont.Load(sPath,""); MergeFont(subfont); } } // Load each font page. for( unsigned i = 0; i < asTexturePaths.size(); ++i ) { const RString &sTexturePath = asTexturePaths[i]; FontPage *pPage = new FontPage; // Grab the page name, eg "foo" from "Normal [foo].png". RString sPagename = GetPageNameFromFileName( sTexturePath ); // Ignore stroke textures if( sTexturePath.find("-stroke") != string::npos ) continue; // Load settings for this page from the INI. FontPageSettings cfg; LoadFontPageSettings( cfg, ini, sTexturePath, "common", sChars ); LoadFontPageSettings( cfg, ini, sTexturePath, sPagename, sChars ); // Load. 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_FontPageTextures.m_pTextureMain->GetNumFrames() ) continue; /* OK */ RageException::Throw( "The font \"%s\" maps \"%s\" to frame %i, but the font only has %i frames.", sTexturePath.c_str(), WcharDisplayText(wchar_t(it->first)).c_str(), it->second, pPage->m_FontPageTextures.m_pTextureMain->GetNumFrames() ); } // 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", sIniPath.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) ARRAYLEN(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. */