2011-03-17 01:47:30 -04:00
# 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 ) ,
2011-03-21 01:40:39 -04:00
m_FontPageTextures ( ) , m_sTexturePath ( " " ) , m_aGlyphs ( ) ,
m_iCharToGlyphNo ( ) { }
2011-03-17 01:47:30 -04:00
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 < int > 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 ; i < m_FontPageTextures . m_pTextureMain - > GetNumFrames ( ) ; i + + )
{
map < int , int > : : 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 ; i < m_FontPageTextures . m_pTextureMain - > GetNumFrames ( ) ; i + + )
aiFrameWidths [ i ] + = cfg . m_iAddToAllWidths ;
}
if ( cfg . m_fScaleAllWidthsBy ! = 1 )
{
for ( int i = 0 ; i < m_FontPageTextures . m_pTextureMain - > GetNumFrames ( ) ; 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 < int > & 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 )
{
2012-02-11 16:27:45 -06:00
// 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 + + ;
2011-03-17 01:47:30 -04:00
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 ; i < szLine . size ( ) ; i + + )
iLineWidth + = GetGlyph ( szLine [ i ] ) . m_iHadvance ;
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 ; i < szLine . size ( ) ; i + + )
iLineHeight = max ( iLineHeight , GetGlyph ( szLine [ i ] ) . m_pPage - > m_iHeight ) ;
return iLineHeight ;
}
2011-03-19 18:26:55 -04:00
Font : : Font ( ) : m_iRefCount ( 1 ) , path ( " " ) , m_apPages ( ) , m_pDefault ( NULL ) ,
2011-03-21 01:40:39 -04:00
m_iCharToGlyph ( ) , m_bRightToLeft ( false ) ,
// strokes aren't shown by default, hence the Color.
m_DefaultStrokeColor ( RageColor ( 0 , 0 , 0 , 0 ) ) , m_sChars ( " " ) { }
2011-03-17 01:47:30 -04:00
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 < wchar_t , int > : : 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 < wchar_t , glyph * > : : 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 < wchar_t , glyph * > : : 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 < wchar_t , glyph * > : : 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 < wchar_t , glyph * > : : 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 < RString > & asTexturePathsOut )
{
RString sPrefix = SetExtension ( sFontIniPath , " " ) ;
vector < RString > 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 ; n < sChars . size ( ) ; n + + )
{
char c = sChars [ n ] ;
cfg . CharToGlyphNo [ c ] = n ;
}
int iNumFramesWide , iNumFramesHigh ;
RageTexture : : GetFrameDimensionsFromFileName ( sTexturePath , & iNumFramesWide , & iNumFramesHigh ) ;
int iNumFrames = iNumFramesWide * iNumFramesHigh ;
// Deal with fonts generated by Bitmap Font Builder.
ini . RenameKey ( " Char Widths " , " main " ) ;
// LOG->Trace("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 ) )
{
2011-05-11 15:58:31 -04:00
cfg . m_mapGlyphWidths [ StringToInt ( sName ) ] = pValue - > GetValue < int > ( ) ;
2011-03-17 01:47:30 -04:00
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 < int > ( ) ;
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 < RString > asMatches ;
static Regex parse ( " ^RANGE ([A-Z0-9 \\ -]+) ( ? # ( [ 0 - 9 A - F ] + ) - ( [ 0 - 9 A - 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 < int > ( ) , 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 ) ) ;
2011-05-11 15:58:31 -04:00
const int iRow = StringToInt ( sRowStr ) ;
2011-03-17 01:47:30 -04:00
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 < RString > ( ) ) ) ;
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 < RString > 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 < RString > 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 < RString > 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 )
{
2011-03-21 02:10:10 -04:00
RString sPath = THEME - > GetPathF ( " " , ImportList [ i ] , true ) ;
if ( sPath = = " " )
2011-03-17 01:47:30 -04:00
{
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 ;
2011-03-21 02:10:10 -04:00
subfont . Load ( sPath , " " ) ;
2011-03-17 01:47:30 -04:00
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 < wchar_t , int > : : 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 < wchar_t , glyph * > : : 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.
*/