diff --git a/stepmania/src/Font.cpp b/stepmania/src/Font.cpp index 4992ff0e66..a3229a696e 100644 --- a/stepmania/src/Font.cpp +++ b/stepmania/src/Font.cpp @@ -24,7 +24,7 @@ FontPage::FontPage() m_pTexture = NULL; } -void FontPage::Load( const CString &TexturePath, IniFile &ini ) +void FontPage::Load( const CString &TexturePath, const FontPageSettings &cfg ) { m_sTexturePath = TexturePath; @@ -34,78 +34,41 @@ void FontPage::Load( const CString &TexturePath, IniFile &ini ) ASSERT( m_pTexture != NULL ); // load character widths - vector FrameWidths; + vector FrameWidths; // = cfg.GlyphWidths; int i; // Assume each character is the width of the frame by default. for( i=0; iGetNumFrames(); i++ ) - FrameWidths.push_back(m_pTexture->GetSourceFrameWidth()); - { - /* Iterate over all keys. */ - const IniFile::key *k = ini.GetKey("main"); - if(k) + map::const_iterator it = cfg.GlyphWidths.find(i); + if(it != cfg.GlyphWidths.end()) { - for(IniFile::key::const_iterator key = k->begin(); key != k->end(); ++key) - { - CString val = key->first; - CString data = key->second; - - val.MakeUpper(); - - /* If val is an integer, it's a width, eg. "10=27". */ - if(IsAnInt(val)) - { - FrameWidths[atoi(val)] = atoi(data); - continue; - } - - /* "map XXXX=frame" maps a char to a frame. */ - if(val.substr(0, 4) == "map ") - { - val = val.substr(4); /* "XXXX" */ - - /* XXXX can be "U+HEX". */ - - int c = -1; - - if(val.substr(0, 2) == "U+" && IsHexVal(val.substr(2))) - sscanf(val.substr(2).c_str(), "%x", &c); - - if(c == -1) - RageException::Throw( "The font '%s' has an invalid INI value '%s'.", - m_sTexturePath.GetString(), val.GetString() ); - - m_iCharToGlyphNo[i] = atoi(data); - } - } + FrameWidths.push_back(it->second); + } else { + FrameWidths.push_back(m_pTexture->GetSourceFrameWidth()); } } - int DrawExtraPixelsLeft = 0, DrawExtraPixelsRight = 0; - - ini.GetValueI( "main", "DrawExtraPixelsLeft", DrawExtraPixelsLeft ); - ini.GetValueI( "main", "DrawExtraPixelsRight", DrawExtraPixelsRight ); - - int iAddToAllWidths = 0; - if( ini.GetValueI( "main", "AddToAllWidths", iAddToAllWidths ) ) + if( cfg.AddToAllWidths ) { for( int i=0; i<256; i++ ) - FrameWidths[i] += iAddToAllWidths; + FrameWidths[i] += cfg.AddToAllWidths; } - float fScaleAllWidthsBy = 0; - if( ini.GetValueF( "main", "ScaleAllWidthsBy", fScaleAllWidthsBy ) ) + if( cfg.ScaleAllWidthsBy != 1 ) { for( int i=0; i<256; i++ ) - FrameWidths[i] = int(roundf( FrameWidths[i] * fScaleAllWidthsBy )); + FrameWidths[i] = int(roundf( FrameWidths[i] * cfg.ScaleAllWidthsBy )); } + m_iCharToGlyphNo = cfg.CharToGlyphNo; + /* All characters on a page have the same vertical spacing. */ - int LineSpacing = m_pTexture->GetSourceFrameHeight(); - ini.GetValueI( "main", "LineSpacing", LineSpacing ); + int LineSpacing = cfg.LineSpacing; + if(LineSpacing == -1) + LineSpacing = m_pTexture->GetSourceFrameHeight(); SetTextureCoords(FrameWidths, LineSpacing); - SetExtraPixels(DrawExtraPixelsLeft, DrawExtraPixelsRight); + SetExtraPixels(cfg.DrawExtraPixelsLeft, cfg.DrawExtraPixelsRight); } void FontPage::SetTextureCoords(const vector &widths, int LineSpacing) @@ -258,23 +221,17 @@ RageTexture *Font::GetGlyphTexture( int c ) return it->second->Texture; } -/* Load font-global settings. */ -void Font::LoadINI(IniFile &ini) +void Font::CapsOnly() { - bool CapitalsOnly = false; - ini.GetValueB( "main", "CapitalsOnly", CapitalsOnly ); - if(CapitalsOnly) + /* For each uppercase character that we have a mapping for, add + * a lowercase one. */ + for(char c = 'A'; c <= 'Z'; ++c) { - /* 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); + map::const_iterator it = m_iCharToGlyph.find(c); - if(it == m_iCharToGlyph.end()) - continue; + if(it == m_iCharToGlyph.end()) + continue; - m_iCharToGlyph[tolower(c)] = it->second; - } + m_iCharToGlyph[tolower(c)] = it->second; } } diff --git a/stepmania/src/Font.h b/stepmania/src/Font.h index d78ca62f6a..a3351e3a2b 100644 --- a/stepmania/src/Font.h +++ b/stepmania/src/Font.h @@ -30,6 +30,26 @@ struct glyph { RectF rect; }; +struct FontPageSettings { + int DrawExtraPixelsLeft, + DrawExtraPixelsRight, + AddToAllWidths, + LineSpacing; + float ScaleAllWidthsBy; + + map CharToGlyphNo; + /* If a value is missing, the width of the texture frame is used. */ + map GlyphWidths; + + FontPageSettings(): + DrawExtraPixelsLeft(0), DrawExtraPixelsRight(0), + AddToAllWidths(0), + LineSpacing(-1), + ScaleAllWidthsBy(1) + { } +}; + + class FontPage { public: @@ -48,7 +68,7 @@ public: int GetLineWidthInSourcePixels( const CString &szLine ); int GetLineHeightInSourcePixels( const CString &szLine ); - void Load( const CString &sASCIITexturePath, IniFile &ini ); + void Load( const CString &sASCIITexturePath, const FontPageSettings &cfg ); private: void SetExtraPixels(int DrawExtraPixelsLeft, int DrawExtraPixelsRight); @@ -75,7 +95,7 @@ public: void AddPage(FontPage *fp); /* Load font-wide settings. */ - void LoadINI(IniFile &ini); + void CapsOnly(); private: vector pages; diff --git a/stepmania/src/FontManager.cpp b/stepmania/src/FontManager.cpp index 857826a95a..e9303efa3b 100644 --- a/stepmania/src/FontManager.cpp +++ b/stepmania/src/FontManager.cpp @@ -19,6 +19,7 @@ #include "RageException.h" #include "RageTimer.h" +static map CharAliases; FontManager* FONT = NULL; @@ -35,15 +36,288 @@ FontManager::~FontManager() LOG->Trace( "FONT LEAK: '%s', RefCount = %d.", i->first.GetString(), pFont->m_iRefCount ); delete pFont; } + } - -Font* FontManager::LoadFont( CString sFontOrTextureFilePath, CString sChars ) +void FontManager::LoadFontPageSettings(FontPageSettings &cfg, IniFile &ini, const CString &PageName, int NumFrames) { - sFontOrTextureFilePath.MakeLower(); + if(NumFrames == 128 || NumFrames == 256) + { + /* If it's 128 or 256 frames, default to ASCII or ISO-8859-1, + * 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(NumFrames == 128) + cfg.MapRange("ASCII", 0, 127, 0); + else if(NumFrames == 256) + cfg.MapRange("ISO-8859-1", 0, 255, 0); + */ + for( int i=0; ibegin(); key != k->end(); ++key) + { + CString val = key->first; + CString data = key->second; + + val.MakeUpper(); + + /* If val is an integer, it's a width, eg. "10=27". */ + if(IsAnInt(val)) + { + cfg.GlyphWidths[atoi(val)] = atoi(data); + continue; + } + + /* "map XXXX=frame" maps a char to a frame. */ + if(val.substr(0, 4) == "MAP ") + { + /* map CODEPOINT=frame. CODEPOINT can be "U+hexval" or an alias. + * XXX: implement aliases + * map 1=2 is the same as + * range unicode #1-1=2 + */ + CString codepoint = val.substr(4); /* "XXXX" */ + + int c = -1; + + if(codepoint.substr(0, 2) == "U+" && IsHexVal(codepoint.substr(2))) + sscanf(codepoint.substr(2).c_str(), "%x", &c); + else if(CharAliases.find(codepoint) != CharAliases.end()) + c = CharAliases[codepoint]; + + if(c == -1) + RageException::Throw( "Font definition '%s' has an invalid value '%s'.", + ini.GetPath().GetString(), val.GetString() ); + + int frame = atoi(data); + if(frame >= NumFrames) + RageException::Throw( "Font definition '%s' maps to frame %i, but font only has %i frames", + ini.GetPath().GetString(), frame, NumFrames ); + + cfg.CharToGlyphNo[c] = frame; + continue; + } + + if(val.substr(0, 6) == "RANGE ") + { + /* range RANGE=first_frame + * + * RANGE is: + * CODESET or + * CODESET #start-end + * eg + * range ISO-8859-1=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 + */ + CString range = val.substr(6); + unsigned first = 0, last = 0xFFFF; /* all */ + unsigned space = range.find_first_of(' '); + if(space != val.npos) + { + + } + + + + continue; + } + + } +} + +/* "Normal.png", "Normal [foo].png", "Normal 16x16.png", and "Normal [foo] 16x16.png" + * are all part of the FontName "Normal". "Normal2 16x16.png" is not. + * + * So, FileName must start with FontName, followed by either " [", " DIGIT" or + * a period. + * + * This is to make sure we don't pull in textures from similarly-named fonts. + */ +bool FontManager::MatchesFont(CString FontName, CString FileName) +{ + FontName.MakeLower(); + FileName.MakeLower(); + + if(FileName.substr(0, FontName.size()) != FontName) + return false; + FileName = FileName.substr(FontName.size()); + + return regex("^( \\[|\\.| [0-9]+x[0-9]+)", FileName); +} + +void FontManager::GetFontPaths(const CString &sFontOrTextureFilePath, + CStringArray &TexturePaths, CString &IniPath) +{ + CString sDrive, sDir, sFName, sExt; + splitpath( false, sFontOrTextureFilePath, sDrive, sDir, sFName, sExt ); + + ASSERT(sExt.CompareNoCase("ini")); /* don't give us an INI! */ + ASSERT(sExt.CompareNoCase("redir")); /* don't give us a redir! */ + + if(!sExt.empty()) + TexturePaths.push_back(sFontOrTextureFilePath); + + /* If we have no extension, we need to search. */ + GetDirListing( sFontOrTextureFilePath + "*.png", TexturePaths, false, true ); + + CStringArray IniPaths; + GetDirListing( sFontOrTextureFilePath + "*.ini", IniPaths, false, true ); + + /* Filter out texture files that aren't actually for this font. */ + unsigned i = 0; + while(i < TexturePaths.size()) + { + if(!MatchesFont(sFontOrTextureFilePath, TexturePaths[i])) + TexturePaths.erase(TexturePaths.begin()+i); + else + i++; + } + + for(i = 0; i < IniPaths.size(); ++i) + if(MatchesFont(sFontOrTextureFilePath, IniPaths[i])) break; + if(i < IniPaths.size()) IniPath = IniPaths[i]; + + ASSERT(!TexturePaths.empty()); /* ThemeManager should have checked this */ +} + +/* Load the named font and return it. */ +Font *FontManager::LoadFontInt(const CString &sFontOrTextureFilePath, CString sChars) +{ + /* The font is not already loaded. Figure out what we have. */ // LOG->Trace( "FontManager::LoadFont(%s).", sFontFilePath.GetString() ); + CStringArray TexturePaths; + CString IniPath; + GetFontPaths(sFontOrTextureFilePath, TexturePaths, IniPath); + + Font* pFont = new Font; + pFont->path = sFontOrTextureFilePath; + + bool CapitalsOnly = false; + + IniFile ini; + if( !IniPath.empty() ) + { + ini.SetPath( IniPath ); + ini.ReadFile(); + ini.GetValueB( "main", "CapitalsOnly", CapitalsOnly ); + } + + /* Load each font page. */ + int expect = -1; + for(unsigned i = 0; i < TexturePaths.size(); ++i) + { + FontPage *fp = new FontPage; + int frames; + { + int wide, high; + RageTexture::GetFrameDimensionsFromFileName(TexturePaths[i], &wide, &high ); + frames = wide*high; + } + + /* Grab the page name, eg "foo" from "Normal [foo].png". */ + CString pagename = GetPageNameFromFileName(TexturePaths[i]); + + FontPageSettings cfg; + if( !sChars.empty() ) + { + /* Map characters to frames. */ + for( int i=0; iLoad(TexturePaths[i], cfg); + if( expect != -1 && fp->m_pTexture->GetNumFrames() < expect ) + RageException::Throw( "The font '%s' has %d frames; expected at least %i frames.", + fp->m_pTexture->GetNumFrames(), expect ); + + LOG->Trace("Adding page %s (%s) to %s; %i glyphs", + TexturePaths[i].GetString(), pagename.GetString(), + sFontOrTextureFilePath.GetString(), fp->m_iCharToGlyphNo.size()); + pFont->AddPage(fp); + } + + if(CapitalsOnly) + pFont->CapsOnly(); + + if(pFont->m_iCharToGlyph.empty()) + LOG->Warn("Font %s has no characters", sFontOrTextureFilePath.GetString()); + + return pFont; +} + +/* 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. + */ +Font* FontManager::LoadFont( const CString &sFontOrTextureFilePath, CString sChars ) +{ + if(CharAliases.empty()) + { + /* XXX; these should be in a data file somewhere (theme metrics?) + * (The comments here are UTF-8; they won't show up in VC6.) */ + CharAliases["kakumei1"] = 0x547D; /* 革 */ + CharAliases["kakumei2"] = 0x9769; /* 命 */ + CharAliases["matsuri"] = 0x796D; /* 祭 */ + CharAliases["oni"] = 0x9B3C; /* 鬼 */ + CharAliases["michi"] = 0x9053; /* 道 */ + CharAliases["futatsu"] = 0x5F10; /* 弐 */ + CharAliases["omega"] = 0x03a9; /* Ω */ + CharAliases["whiteheart"] = 0x2661; /* ♡ */ + CharAliases["blackstar"] = 0x2605; /* ★ */ + } // Convert the path to lowercase so that we don't load duplicates. // Really, this does not solve the duplicate problem. We could have to copies @@ -57,64 +331,16 @@ Font* FontManager::LoadFont( CString sFontOrTextureFilePath, CString sChars ) pFont->m_iRefCount++; return pFont; } - - // the texture is not already loaded - CString sDrive, sDir, sFName, sExt; - splitpath( false, sFontOrTextureFilePath, sDrive, sDir, sFName, sExt ); - Font* pFont = new Font; - - - FontPage *fp = new FontPage; - IniFile ini; - - int expect; - if( sChars == "" ) - { - /* Default to 0..255. */ - for( int i=0; i<256; i++ ) - fp->m_iCharToGlyphNo[i] = i; - - // Find .ini widths path from texture path - CString sDir, sFileName, sExtension; - splitrelpath( sFontOrTextureFilePath, sDir, sFileName, sExtension ); - const CString sIniPath = sDir + sFileName + ".ini"; - - ini.SetPath( sIniPath ); - ini.ReadFile(); - ini.RenameKey("Char Widths", "main"); - expect = 256; - } - else - { - /* Map characters to frames; we don't actually have an INI. */ - for( int i=0; im_iCharToGlyphNo[c] = i; - } - expect = sChars.GetLength(); - } - - fp->Load(sFontOrTextureFilePath, ini); - if( fp->m_pTexture->GetNumFrames() != expect ) - RageException::Throw( "The font '%s' has %d frames; expected %i frames.", - fp->m_pTexture->GetNumFrames(), expect ); - -// LOG->Trace( "FontManager: Loading '%s' from disk.", sFontFilePath.GetString()); - - pFont->AddPage(fp); - m_mapPathToFont[sFontOrTextureFilePath] = pFont; - - pFont->LoadINI(ini); - - return pFont; + Font *f = LoadFontInt(sFontOrTextureFilePath, sChars); + m_mapPathToFont[sFontOrTextureFilePath] = f; + return f; } void FontManager::UnloadFont( Font *fp ) { -// LOG->Trace( "FontManager::UnloadTexture(%s).", sFontFilePath.GetString() ); + LOG->Trace( "FontManager::UnloadTexture(%s).", fp->path.GetString() ); for( std::map::iterator i = m_mapPathToFont.begin(); i != m_mapPathToFont.end(); ++i) @@ -145,5 +371,5 @@ CString FontManager::GetPageNameFromFileName(const CString &fn) if(end == fn.npos) return "main"; begin++; end--; if(end == begin) return "main"; - return fn.substr(begin+1, end-begin+1); + return fn.substr(begin, end-begin+1); } diff --git a/stepmania/src/FontManager.h b/stepmania/src/FontManager.h index 93cef8fa7c..1d2b81bebd 100644 --- a/stepmania/src/FontManager.h +++ b/stepmania/src/FontManager.h @@ -24,13 +24,18 @@ public: FontManager(); ~FontManager(); - Font* LoadFont( CString sFontOrTextureFilePath, CString sChars = "" ); + Font* LoadFont( const CString &sFontOrTextureFilePath, CString sChars = "" ); void UnloadFont( Font *fp ); protected: // map from file name to a texture holder map m_mapPathToFont; - static CString FontManager::GetPageNameFromFileName(const CString &fn); + static Font *LoadFontInt(const CString &sFontOrTextureFilePath, CString sChars); + static bool MatchesFont(CString FontName, CString FileName); + static CString GetPageNameFromFileName(const CString &fn); + static void LoadFontPageSettings(FontPageSettings &cfg, IniFile &ini, const CString &PageName, int NumFrames); + static void GetFontPaths(const CString &sFontOrTextureFilePath, + CStringArray &TexturePaths, CString &IniPath); }; extern FontManager* FONT; // global and accessable from anywhere in our program diff --git a/stepmania/src/ThemeManager.cpp b/stepmania/src/ThemeManager.cpp index adc50762b5..bef1b54e1c 100644 --- a/stepmania/src/ThemeManager.cpp +++ b/stepmania/src/ThemeManager.cpp @@ -128,16 +128,6 @@ try_element_again: CStringArray asPossibleElementFilePaths; - // look for a redirect - GetDirListing( sCurrentThemeDir + sAssetCategory+"\\"+sFileName + "*.redir", asPossibleElementFilePaths, false, true ); -/* if( !asPossibleElementFilePaths.empty() ) - { - ifstream file(asPossibleElementFilePaths[0]); - CString sLine; - getline(file, sLine); - } - -*/ /////////////////////////////////////// // Search both the current theme and the default theme dirs for this element /////////////////////////////////////// @@ -157,13 +147,24 @@ try_element_again: else if( sAssetCategory == "bganimations" ) asset_masks = bganimations_masks; else ASSERT(0); // Unknown theme asset category int i; + CString path; for(i = 0; asset_masks[i]; ++i) - GetDirListing( sCurrentThemeDir + sAssetCategory+"\\"+sFileName + asset_masks[i], - asPossibleElementFilePaths, false, true ); - for(i = 0; asset_masks[i]; ++i) - GetDirListing( sDefaultThemeDir + sAssetCategory+"\\"+sFileName + asset_masks[i], + { + path = sCurrentThemeDir; + GetDirListing( path + sAssetCategory+"\\"+sFileName + asset_masks[i], asPossibleElementFilePaths, false, true ); + } + if(asPossibleElementFilePaths.empty()) + for(i = 0; asset_masks[i]; ++i) + { + path = sDefaultThemeDir; + + GetDirListing( path + sAssetCategory+"\\"+sFileName + asset_masks[i], + asPossibleElementFilePaths, false, true ); + } + + /* If it's empty, we found nothing. */ if( asPossibleElementFilePaths.empty() ) { #ifdef _DEBUG @@ -193,12 +194,21 @@ try_element_again: } #endif } - asPossibleElementFilePaths[0].MakeLower(); + if( asPossibleElementFilePaths[0].GetLength() > 5 && asPossibleElementFilePaths[0].Right(5) == "redir" ) // this is a redirect file { CString sNewFilePath = DerefRedir(asPossibleElementFilePaths[0]); - if( sNewFilePath == "" || !DoesFileExist(sNewFilePath) ) + if( sAssetCategory == "fonts" ) + { + /* backwards-compatibility hack */ + if( sAssetCategory == "fonts" ) + sNewFilePath.Replace(" 16x16.png", ""); + + return sNewFilePath; + } + + if( !DoesFileExist(sNewFilePath) ) { CString message = ssprintf( "The redirect '%s' points to the file '%s', which does not exist." @@ -211,10 +221,16 @@ try_element_again: #endif RageException::Throw( "%s", message.GetString() ); } - else - return sNewFilePath; + + + return sNewFilePath; } + /* If we're searching for a font, the return value should be the file + * prefix we searched for; don't resolve it to the complete filename. */ + if( sAssetCategory == "fonts" ) + return path + sAssetCategory+"\\"+sFileName; + return asPossibleElementFilePaths[0]; }