2003-02-16 04:01:45 +00:00
|
|
|
#include "global.h"
|
2002-03-30 20:00:13 +00:00
|
|
|
/*
|
|
|
|
|
-----------------------------------------------------------------------------
|
|
|
|
|
Class: Font
|
|
|
|
|
|
|
|
|
|
Desc: See header.
|
|
|
|
|
|
2003-01-04 05:20:40 +00:00
|
|
|
Copyright (c) 2001-2003 by the person(s) listed below. All rights reserved.
|
2002-03-30 20:00:13 +00:00
|
|
|
Chris Danford
|
2003-01-04 05:17:51 +00:00
|
|
|
Glenn Maynard
|
2002-03-30 20:00:13 +00:00
|
|
|
-----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "Font.h"
|
|
|
|
|
#include "IniFile.h"
|
2002-08-23 01:06:36 +00:00
|
|
|
|
2002-03-30 20:00:13 +00:00
|
|
|
#include "RageTextureManager.h"
|
|
|
|
|
#include "RageUtil.h"
|
2002-05-01 19:14:55 +00:00
|
|
|
#include "RageLog.h"
|
2002-07-27 19:29:51 +00:00
|
|
|
#include "RageException.h"
|
2003-01-05 07:55:31 +00:00
|
|
|
#include "FontManager.h"
|
2003-01-06 02:50:25 +00:00
|
|
|
#include "GameState.h"
|
2003-01-10 02:02:51 +00:00
|
|
|
#include "ThemeManager.h"
|
|
|
|
|
#include "GameManager.h"
|
2003-01-16 20:21:07 +00:00
|
|
|
#include "FontCharmaps.h"
|
|
|
|
|
#include "FontCharAliases.h"
|
2003-01-05 07:55:31 +00:00
|
|
|
|
2003-01-28 00:45:01 +00:00
|
|
|
/* Last private-use Unicode character: */
|
|
|
|
|
const wchar_t Font::DEFAULT_GLYPH = 0xF8FF;
|
2002-06-14 22:25:22 +00:00
|
|
|
|
2003-01-04 05:17:51 +00:00
|
|
|
FontPage::FontPage()
|
2002-03-30 20:00:13 +00:00
|
|
|
{
|
2003-01-04 05:17:51 +00:00
|
|
|
m_pTexture = NULL;
|
2003-01-03 20:59:50 +00:00
|
|
|
}
|
|
|
|
|
|
2003-01-18 03:14:10 +00:00
|
|
|
void FontPage::Load( FontPageSettings cfg )
|
2003-01-03 20:59:50 +00:00
|
|
|
{
|
2003-01-10 00:31:46 +00:00
|
|
|
m_sTexturePath = cfg.TexturePath;
|
2002-03-30 20:00:13 +00:00
|
|
|
|
2002-09-03 22:31:06 +00:00
|
|
|
// load texture
|
2003-01-11 21:20:35 +00:00
|
|
|
RageTextureID ID(m_sTexturePath);
|
2003-10-08 02:54:40 +00:00
|
|
|
if( cfg.TextureHints != "default" )
|
|
|
|
|
ID.AdditionalTextureHints = cfg.TextureHints;
|
|
|
|
|
else
|
2003-10-08 23:18:56 +00:00
|
|
|
ID.AdditionalTextureHints = "16bpp";
|
2003-01-11 21:20:35 +00:00
|
|
|
|
2003-01-15 02:10:58 +00:00
|
|
|
m_pTexture = TEXTUREMAN->LoadTexture( ID );
|
2003-06-06 20:30:57 +00:00
|
|
|
ASSERT( m_pTexture != NULL );
|
2002-03-30 20:00:13 +00:00
|
|
|
|
2002-09-03 22:31:06 +00:00
|
|
|
// load character widths
|
2003-01-05 05:13:45 +00:00
|
|
|
vector<int> FrameWidths;
|
2003-01-03 22:53:22 +00:00
|
|
|
int i;
|
2003-01-16 20:21:07 +00:00
|
|
|
|
|
|
|
|
int default_width = m_pTexture->GetSourceFrameWidth();
|
|
|
|
|
if(cfg.DefaultWidth != -1)
|
|
|
|
|
default_width = cfg.DefaultWidth;
|
|
|
|
|
|
2003-01-03 22:53:22 +00:00
|
|
|
// Assume each character is the width of the frame by default.
|
2003-01-04 05:17:51 +00:00
|
|
|
for( i=0; i<m_pTexture->GetNumFrames(); i++ )
|
2003-01-03 22:53:22 +00:00
|
|
|
{
|
2003-01-05 03:32:41 +00:00
|
|
|
map<int,int>::const_iterator it = cfg.GlyphWidths.find(i);
|
|
|
|
|
if(it != cfg.GlyphWidths.end())
|
2003-01-03 22:53:22 +00:00
|
|
|
{
|
2003-01-05 03:32:41 +00:00
|
|
|
FrameWidths.push_back(it->second);
|
|
|
|
|
} else {
|
2003-01-16 20:21:07 +00:00
|
|
|
FrameWidths.push_back(default_width);
|
2003-01-03 22:53:22 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2003-01-05 03:32:41 +00:00
|
|
|
if( cfg.AddToAllWidths )
|
2002-03-30 20:00:13 +00:00
|
|
|
{
|
2003-01-16 20:21:07 +00:00
|
|
|
for( int i=0; i<m_pTexture->GetNumFrames(); i++ )
|
2003-01-05 03:32:41 +00:00
|
|
|
FrameWidths[i] += cfg.AddToAllWidths;
|
2002-03-30 20:00:13 +00:00
|
|
|
}
|
2002-03-31 07:55:25 +00:00
|
|
|
|
2003-01-05 03:32:41 +00:00
|
|
|
if( cfg.ScaleAllWidthsBy != 1 )
|
2002-09-04 03:49:08 +00:00
|
|
|
{
|
2003-01-16 20:21:07 +00:00
|
|
|
for( int i=0; i<m_pTexture->GetNumFrames(); i++ )
|
2003-01-05 03:32:41 +00:00
|
|
|
FrameWidths[i] = int(roundf( FrameWidths[i] * cfg.ScaleAllWidthsBy ));
|
2002-09-04 03:49:08 +00:00
|
|
|
}
|
|
|
|
|
|
2003-01-05 03:32:41 +00:00
|
|
|
m_iCharToGlyphNo = cfg.CharToGlyphNo;
|
|
|
|
|
|
2003-01-08 02:32:30 +00:00
|
|
|
LineSpacing = cfg.LineSpacing;
|
2003-01-05 03:32:41 +00:00
|
|
|
if(LineSpacing == -1)
|
|
|
|
|
LineSpacing = m_pTexture->GetSourceFrameHeight();
|
2002-03-30 20:00:13 +00:00
|
|
|
|
2003-01-08 02:32:30 +00:00
|
|
|
int baseline=0;
|
2003-01-18 03:14:10 +00:00
|
|
|
/* 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.Baseline == -1)
|
2003-01-08 02:32:30 +00:00
|
|
|
{
|
|
|
|
|
float center = m_pTexture->GetSourceFrameHeight()/2.0f;
|
2003-01-18 03:14:10 +00:00
|
|
|
cfg.Baseline = int(center + LineSpacing/2);
|
2003-01-08 02:32:30 +00:00
|
|
|
}
|
2003-01-18 03:14:10 +00:00
|
|
|
if(cfg.Top == -1)
|
2003-01-08 02:32:30 +00:00
|
|
|
{
|
2003-01-18 03:14:10 +00:00
|
|
|
float center = m_pTexture->GetSourceFrameHeight()/2.0f;
|
|
|
|
|
cfg.Top = int(center - LineSpacing/2);
|
2003-01-08 02:32:30 +00:00
|
|
|
}
|
2003-01-18 03:14:10 +00:00
|
|
|
baseline = cfg.Baseline;
|
|
|
|
|
height = baseline-cfg.Top;
|
2003-01-08 02:32:30 +00:00
|
|
|
|
|
|
|
|
/* Shift the character up so the top will be rendered at the baseline. */
|
2003-01-10 03:48:21 +00:00
|
|
|
vshift = (float) -baseline;
|
2003-01-08 02:32:30 +00:00
|
|
|
|
2003-07-06 05:32:45 +00:00
|
|
|
SetTextureCoords(FrameWidths, cfg.AdvanceExtraPixels);
|
2003-01-05 03:32:41 +00:00
|
|
|
SetExtraPixels(cfg.DrawExtraPixelsLeft, cfg.DrawExtraPixelsRight);
|
2003-01-08 02:32:30 +00:00
|
|
|
|
2003-01-24 01:35:05 +00:00
|
|
|
// LOG->Trace("Font %s: height %i, baseline %i ( == top %i)",
|
2003-04-25 00:01:35 +00:00
|
|
|
// m_sTexturePath.c_str(), height, baseline, baseline-height);
|
2002-03-30 20:00:13 +00:00
|
|
|
}
|
|
|
|
|
|
2003-07-06 05:32:45 +00:00
|
|
|
void FontPage::SetTextureCoords(const vector<int> &widths, int AdvanceExtraPixels)
|
2003-01-03 20:59:50 +00:00
|
|
|
{
|
2003-06-06 20:30:57 +00:00
|
|
|
for(int i = 0; i < m_pTexture->GetNumFrames(); ++i)
|
2003-01-03 20:59:50 +00:00
|
|
|
{
|
2003-06-06 20:30:57 +00:00
|
|
|
glyph g;
|
2003-01-03 20:59:50 +00:00
|
|
|
|
2003-06-06 20:30:57 +00:00
|
|
|
g.fp = this;
|
2003-01-03 20:59:50 +00:00
|
|
|
|
2003-06-06 20:30:57 +00:00
|
|
|
/* 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.rect = *m_pTexture->GetTextureCoordRect(i);;
|
2003-01-03 22:53:22 +00:00
|
|
|
|
2003-06-06 20:30:57 +00:00
|
|
|
/* Set the width and height to the width and line spacing, respectively. */
|
|
|
|
|
g.width = float(widths[i]);
|
|
|
|
|
g.height = float(m_pTexture->GetSourceFrameHeight());
|
2003-06-05 19:29:27 +00:00
|
|
|
|
2003-07-06 05:32:45 +00:00
|
|
|
g.hadvance = int(g.width) + AdvanceExtraPixels;
|
2003-06-05 19:29:27 +00:00
|
|
|
|
2003-06-06 20:30:57 +00:00
|
|
|
/* Do the same thing with X. Do this by changing the actual rendered
|
|
|
|
|
* rect, instead of shifting it, so we don't render more than we need to. */
|
|
|
|
|
g.hshift = 0;
|
|
|
|
|
{
|
2003-06-21 20:05:37 +00:00
|
|
|
int iSourcePixelsToChopOff = m_pTexture->GetSourceFrameWidth() - widths[i];
|
|
|
|
|
if((iSourcePixelsToChopOff % 2) == 1)
|
2003-01-04 07:55:42 +00:00
|
|
|
{
|
2003-06-06 20:30:57 +00:00
|
|
|
/* 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 hadvance 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. */
|
2003-06-21 20:05:37 +00:00
|
|
|
iSourcePixelsToChopOff--;
|
2003-06-06 20:30:57 +00:00
|
|
|
g.width++;
|
2003-01-04 07:55:42 +00:00
|
|
|
}
|
2003-06-21 20:05:37 +00:00
|
|
|
|
|
|
|
|
const float fTexCoordsToChopOff = iSourcePixelsToChopOff * m_pTexture->GetSourceToTexCoordsRatio();
|
2003-01-04 01:47:01 +00:00
|
|
|
|
2003-06-06 20:30:57 +00:00
|
|
|
g.rect.left += fTexCoordsToChopOff/2;
|
|
|
|
|
g.rect.right -= fTexCoordsToChopOff/2;
|
2003-06-05 19:29:27 +00:00
|
|
|
}
|
2003-06-06 20:30:57 +00:00
|
|
|
|
|
|
|
|
g.Texture = m_pTexture;
|
|
|
|
|
|
|
|
|
|
glyphs.push_back(g);
|
2003-01-03 20:59:50 +00:00
|
|
|
}
|
2002-08-13 23:26:46 +00:00
|
|
|
}
|
|
|
|
|
|
2003-01-04 05:17:51 +00:00
|
|
|
void FontPage::SetExtraPixels(int DrawExtraPixelsLeft, int DrawExtraPixelsRight)
|
2003-01-03 20:59:50 +00:00
|
|
|
{
|
2003-01-04 05:17:51 +00:00
|
|
|
/* Hack: do one more than we were asked to; I think a lot of fonts are one
|
|
|
|
|
* too low. */
|
|
|
|
|
DrawExtraPixelsRight++;
|
|
|
|
|
DrawExtraPixelsLeft++;
|
|
|
|
|
|
2003-01-04 07:55:42 +00:00
|
|
|
if((DrawExtraPixelsLeft % 2) == 1)
|
|
|
|
|
DrawExtraPixelsLeft++;
|
|
|
|
|
|
2003-01-03 20:59:50 +00:00
|
|
|
/* Adjust for DrawExtraPixelsLeft and DrawExtraPixelsRight. */
|
2003-01-03 22:53:22 +00:00
|
|
|
for(unsigned i = 0; i < glyphs.size(); ++i)
|
2003-01-03 20:59:50 +00:00
|
|
|
{
|
|
|
|
|
int iFrameWidth = m_pTexture->GetSourceFrameWidth();
|
2003-01-04 07:55:42 +00:00
|
|
|
float iCharWidth = glyphs[i].width;
|
2003-01-03 20:59:50 +00:00
|
|
|
|
2003-01-04 07:55:42 +00:00
|
|
|
/* Extra pixels to draw to the left and right. We don't have to
|
|
|
|
|
* worry about alignment here; CharWidth is always even (by
|
|
|
|
|
* SetTextureCoords) and iFrameWidth are almost always even. */
|
2003-01-04 01:47:01 +00:00
|
|
|
float ExtraLeft = min( float(DrawExtraPixelsLeft), (iFrameWidth-iCharWidth)/2.0f );
|
|
|
|
|
float ExtraRight = min( float(DrawExtraPixelsRight), (iFrameWidth-iCharWidth)/2.0f );
|
2003-01-03 20:59:50 +00:00
|
|
|
|
|
|
|
|
/* Move left and expand right. */
|
2003-06-21 20:05:37 +00:00
|
|
|
glyphs[i].rect.left -= ExtraLeft * m_pTexture->GetSourceToTexCoordsRatio();
|
|
|
|
|
glyphs[i].rect.right += ExtraRight * m_pTexture->GetSourceToTexCoordsRatio();
|
2003-01-04 01:47:01 +00:00
|
|
|
glyphs[i].hshift -= ExtraLeft;
|
|
|
|
|
glyphs[i].width += ExtraLeft + ExtraRight;
|
2003-01-03 20:59:50 +00:00
|
|
|
}
|
|
|
|
|
}
|
2002-08-13 23:26:46 +00:00
|
|
|
|
2003-01-04 05:17:51 +00:00
|
|
|
FontPage::~FontPage()
|
2002-03-30 20:00:13 +00:00
|
|
|
{
|
2003-06-06 20:30:57 +00:00
|
|
|
if( m_pTexture != NULL )
|
2003-01-03 22:53:22 +00:00
|
|
|
TEXTUREMAN->UnloadTexture( m_pTexture );
|
2002-03-30 20:00:13 +00:00
|
|
|
}
|
|
|
|
|
|
2003-01-28 00:45:01 +00:00
|
|
|
int Font::GetLineWidthInSourcePixels( const wstring &szLine ) const
|
2002-03-30 20:00:13 +00:00
|
|
|
{
|
2003-01-04 07:55:42 +00:00
|
|
|
int LineWidth = 0;
|
2002-03-30 20:00:13 +00:00
|
|
|
|
2002-12-29 22:55:26 +00:00
|
|
|
for( unsigned i=0; i<szLine.size(); i++ )
|
2003-01-04 07:55:42 +00:00
|
|
|
LineWidth += GetGlyph(szLine[i]).hadvance;
|
2002-03-30 20:00:13 +00:00
|
|
|
|
2003-01-04 07:55:42 +00:00
|
|
|
return LineWidth;
|
2002-03-30 20:00:13 +00:00
|
|
|
}
|
|
|
|
|
|
2003-01-28 00:45:01 +00:00
|
|
|
int Font::GetLineHeightInSourcePixels( const wstring &szLine ) const
|
2003-01-04 01:47:01 +00:00
|
|
|
{
|
2003-01-08 02:32:30 +00:00
|
|
|
int iLineHeight = 0;
|
2003-01-04 05:17:51 +00:00
|
|
|
|
2003-03-27 08:04:28 +00:00
|
|
|
/* The height of a line is the height of its tallest used font page. */
|
2003-01-04 07:55:42 +00:00
|
|
|
for( unsigned i=0; i<szLine.size(); i++ )
|
2003-01-08 02:32:30 +00:00
|
|
|
iLineHeight = max(iLineHeight, GetGlyph(szLine[i]).fp->height);
|
2003-01-10 03:54:29 +00:00
|
|
|
|
2003-01-08 02:32:30 +00:00
|
|
|
return iLineHeight;
|
|
|
|
|
}
|
2003-01-04 05:17:51 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
Font::Font()
|
|
|
|
|
{
|
2003-04-25 00:01:35 +00:00
|
|
|
//LOG->Trace( "Font::LoadFromFontName(%s)", sASCIITexturePath.c_str() );
|
2003-01-04 05:17:51 +00:00
|
|
|
|
|
|
|
|
m_iRefCount = 1;
|
2003-01-10 00:31:46 +00:00
|
|
|
def = NULL;
|
2003-01-04 05:17:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Font::~Font()
|
2003-01-10 00:31:46 +00:00
|
|
|
{
|
|
|
|
|
Unload();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Font::Unload()
|
2003-01-04 05:17:51 +00:00
|
|
|
{
|
2003-01-07 08:16:55 +00:00
|
|
|
unsigned i;
|
|
|
|
|
|
|
|
|
|
for(i = 0; i < pages.size(); ++i)
|
2003-01-04 05:17:51 +00:00
|
|
|
delete pages[i];
|
2003-01-10 00:31:46 +00:00
|
|
|
pages.clear();
|
|
|
|
|
|
|
|
|
|
m_iCharToGlyph.clear();
|
|
|
|
|
def = NULL;
|
|
|
|
|
|
|
|
|
|
/* Don't clear the refcount. We've unloaded, but that doesn't mean things
|
|
|
|
|
* aren't still pointing to us. */
|
2003-01-04 01:47:01 +00:00
|
|
|
}
|
2002-03-30 20:00:13 +00:00
|
|
|
|
2003-01-10 02:02:51 +00:00
|
|
|
void Font::Reload()
|
|
|
|
|
{
|
|
|
|
|
Unload();
|
|
|
|
|
ASSERT(!path.empty());
|
|
|
|
|
Load(path, Chars);
|
|
|
|
|
}
|
|
|
|
|
|
2003-01-10 00:31:46 +00:00
|
|
|
|
2003-01-04 05:17:51 +00:00
|
|
|
void Font::AddPage(FontPage *fp)
|
|
|
|
|
{
|
|
|
|
|
pages.push_back(fp);
|
|
|
|
|
|
2003-01-05 08:11:05 +00:00
|
|
|
for(map<longchar,int>::const_iterator it = fp->m_iCharToGlyphNo.begin();
|
2003-01-04 05:17:51 +00:00
|
|
|
it != fp->m_iCharToGlyphNo.end(); ++it)
|
|
|
|
|
{
|
|
|
|
|
m_iCharToGlyph[it->first] = &fp->glyphs[it->second];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2003-01-10 02:02:51 +00:00
|
|
|
void Font::MergeFont(Font &f)
|
2003-01-05 07:55:31 +00:00
|
|
|
{
|
2003-01-16 22:49:22 +00:00
|
|
|
/* 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(def == NULL)
|
|
|
|
|
def = f.def;
|
|
|
|
|
|
2003-01-10 02:02:51 +00:00
|
|
|
for(map<longchar,glyph*>::iterator it = f.m_iCharToGlyph.begin();
|
|
|
|
|
it != f.m_iCharToGlyph.end(); ++it)
|
2003-01-05 07:55:31 +00:00
|
|
|
{
|
|
|
|
|
m_iCharToGlyph[it->first] = it->second;
|
|
|
|
|
}
|
|
|
|
|
|
2003-01-10 02:02:51 +00:00
|
|
|
pages.insert(pages.end(), f.pages.begin(), f.pages.end());
|
|
|
|
|
|
|
|
|
|
f.pages.clear();
|
2003-01-05 07:55:31 +00:00
|
|
|
}
|
|
|
|
|
|
2003-01-28 00:45:01 +00:00
|
|
|
const glyph &Font::GetGlyph( wchar_t c ) const
|
2003-01-04 05:17:51 +00:00
|
|
|
{
|
2003-01-06 04:24:28 +00:00
|
|
|
ASSERT(c >= 0 && c <= 0xFFFFFF);
|
|
|
|
|
|
2003-01-06 02:50:25 +00:00
|
|
|
/* See if there's a game-specific version of this character. */
|
|
|
|
|
int gc = FontManager::MakeGameGlyph(c, GAMESTATE->m_CurGame);
|
|
|
|
|
map<longchar,glyph*>::const_iterator it = m_iCharToGlyph.find(gc);
|
2003-01-04 05:17:51 +00:00
|
|
|
|
2003-01-06 02:50:25 +00:00
|
|
|
/* If there isn't, try the regular character. */
|
|
|
|
|
if(it == m_iCharToGlyph.end()) it = m_iCharToGlyph.find(c);
|
2003-01-04 05:17:51 +00:00
|
|
|
|
2003-01-06 02:50:25 +00:00
|
|
|
/* If *that's* missing, use the default glyph. */
|
|
|
|
|
if(it == m_iCharToGlyph.end()) it = m_iCharToGlyph.find(DEFAULT_GLYPH);
|
|
|
|
|
|
|
|
|
|
if(it == m_iCharToGlyph.end())
|
2003-04-25 00:01:35 +00:00
|
|
|
RageException::Throw( "The default glyph is missing from the font '%s'", path.c_str() );
|
2003-01-06 02:50:25 +00:00
|
|
|
|
2003-01-04 05:17:51 +00:00
|
|
|
return *it->second;
|
|
|
|
|
}
|
|
|
|
|
|
2003-02-12 19:33:30 +00:00
|
|
|
bool Font::FontCompleteForString( const wstring &str ) const
|
|
|
|
|
{
|
|
|
|
|
map<longchar,glyph*>::const_iterator def = m_iCharToGlyph.find(DEFAULT_GLYPH);
|
|
|
|
|
if(def == m_iCharToGlyph.end())
|
2003-04-25 00:01:35 +00:00
|
|
|
RageException::Throw( "The default glyph is missing from the font '%s'", path.c_str() );
|
2003-02-12 19:33:30 +00:00
|
|
|
|
|
|
|
|
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 == def->second)
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2003-01-05 03:32:41 +00:00
|
|
|
void Font::CapsOnly()
|
2003-01-04 05:17:51 +00:00
|
|
|
{
|
2003-01-05 03:32:41 +00:00
|
|
|
/* For each uppercase character that we have a mapping for, add
|
|
|
|
|
* a lowercase one. */
|
|
|
|
|
for(char c = 'A'; c <= 'Z'; ++c)
|
2003-01-04 05:17:51 +00:00
|
|
|
{
|
2003-01-05 08:11:05 +00:00
|
|
|
map<longchar,glyph*>::const_iterator it = m_iCharToGlyph.find(c);
|
2003-01-04 05:17:51 +00:00
|
|
|
|
2003-01-05 03:32:41 +00:00
|
|
|
if(it == m_iCharToGlyph.end())
|
|
|
|
|
continue;
|
2003-01-04 05:17:51 +00:00
|
|
|
|
2003-01-05 05:13:45 +00:00
|
|
|
m_iCharToGlyph[(char) tolower(c)] = it->second;
|
2003-01-04 05:17:51 +00:00
|
|
|
}
|
|
|
|
|
}
|
2003-01-06 23:49:43 +00:00
|
|
|
|
|
|
|
|
void Font::SetDefaultGlyph(FontPage *fp)
|
|
|
|
|
{
|
|
|
|
|
ASSERT(fp);
|
|
|
|
|
ASSERT(!fp->glyphs.empty());
|
|
|
|
|
def = fp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2003-01-16 23:22:21 +00:00
|
|
|
CString Font::GetFontName(CString FileName)
|
2003-01-16 22:49:22 +00:00
|
|
|
{
|
|
|
|
|
CString orig = FileName;
|
|
|
|
|
|
2003-01-16 23:22:21 +00:00
|
|
|
CString sDir, sFName, sExt;
|
|
|
|
|
splitpath( FileName, sDir, sFName, sExt );
|
|
|
|
|
FileName = sFName;
|
|
|
|
|
|
2003-03-27 08:04:28 +00:00
|
|
|
/* If it ends in an extension, remove it. */
|
|
|
|
|
static Regex drop_ext("\\....$");
|
2003-01-23 04:25:04 +00:00
|
|
|
if(drop_ext.Compare(FileName))
|
2003-01-16 22:49:22 +00:00
|
|
|
FileName.erase(FileName.size()-4);
|
|
|
|
|
|
2003-05-13 04:08:19 +00:00
|
|
|
/* If it ends in a resolution spec, remove it. */
|
2003-01-16 22:49:22 +00:00
|
|
|
CStringArray mat;
|
2003-05-13 04:08:19 +00:00
|
|
|
static Regex ResSpec("( \\(res [0-9]+x[0-9]+\\))$");
|
|
|
|
|
if(ResSpec.Compare(FileName, mat))
|
|
|
|
|
FileName.erase(FileName.size()-mat[0].size());
|
|
|
|
|
|
|
|
|
|
/* If it ends in a dimension spec, remove it. */
|
2003-01-23 04:25:04 +00:00
|
|
|
static Regex DimSpec("( [0-9]+x[0-9]+)$");
|
|
|
|
|
if(DimSpec.Compare(FileName, mat))
|
2003-01-16 22:49:22 +00:00
|
|
|
FileName.erase(FileName.size()-mat[0].size());
|
|
|
|
|
|
2003-09-03 09:03:58 +00:00
|
|
|
/* If it ends in texture hints, remove them. */
|
|
|
|
|
static Regex Hints("( \\([^\\)]+\\))$");
|
|
|
|
|
if(Hints.Compare(FileName, mat))
|
|
|
|
|
FileName.erase(FileName.size()-mat[0].size());
|
|
|
|
|
|
2003-01-16 22:49:22 +00:00
|
|
|
/* If it ends in a page name, remove it. */
|
2003-01-23 04:25:04 +00:00
|
|
|
static Regex PageName("( \\[.+\\])$");
|
|
|
|
|
if(PageName.Compare(FileName, mat))
|
2003-01-16 22:49:22 +00:00
|
|
|
FileName.erase(FileName.size()-mat[0].size());
|
|
|
|
|
|
|
|
|
|
TrimRight(FileName);
|
|
|
|
|
|
|
|
|
|
if(FileName.empty())
|
2003-04-25 00:01:35 +00:00
|
|
|
RageException::Throw("Can't parse font filename \"%s\"", orig.c_str());
|
2003-01-16 22:49:22 +00:00
|
|
|
|
2003-01-16 23:22:21 +00:00
|
|
|
FileName.MakeLower();
|
2003-01-16 22:49:22 +00:00
|
|
|
return FileName;
|
|
|
|
|
}
|
|
|
|
|
|
2003-01-16 23:22:21 +00:00
|
|
|
|
2003-01-23 04:25:04 +00:00
|
|
|
void Font::WeedFontNames(vector<CString> &v, const CString &FileName)
|
|
|
|
|
{
|
|
|
|
|
CString FontName = Font::GetFontName(FileName);
|
|
|
|
|
/* 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(FontName.CompareNoCase(Font::GetFontName(v[i])))
|
|
|
|
|
v.erase(v.begin()+i);
|
|
|
|
|
else i++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2003-01-16 22:49:22 +00:00
|
|
|
/* 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,
|
2003-01-10 02:02:51 +00:00
|
|
|
CStringArray &TexturePaths, CString &IniPath)
|
|
|
|
|
{
|
2003-01-16 22:49:22 +00:00
|
|
|
CString sDir, sFName, sExt;
|
|
|
|
|
splitpath( sFontOrTextureFilePath, sDir, sFName, sExt );
|
2003-01-10 02:02:51 +00:00
|
|
|
|
2003-01-16 22:49:22 +00:00
|
|
|
/* Don't give us a redir; resolve those before sending them here. */
|
|
|
|
|
ASSERT(sExt.CompareNoCase("redir"));
|
2003-01-10 02:02:51 +00:00
|
|
|
|
2003-01-16 22:49:22 +00:00
|
|
|
/* sFName can't be empty, or we don't know what to search for. */
|
|
|
|
|
ASSERT(!sFName.empty());
|
2003-01-10 02:02:51 +00:00
|
|
|
|
2003-01-16 22:49:22 +00:00
|
|
|
CString FontName = GetFontName(sFName);
|
2003-01-10 02:02:51 +00:00
|
|
|
|
2003-01-16 22:49:22 +00:00
|
|
|
CStringArray Files;
|
2003-01-23 04:31:14 +00:00
|
|
|
GetDirListing( sDir+FontName + "*", Files, false, false );
|
2003-01-10 02:02:51 +00:00
|
|
|
|
2003-01-16 22:49:22 +00:00
|
|
|
for(unsigned i = 0; i < Files.size(); ++i)
|
2003-01-10 02:02:51 +00:00
|
|
|
{
|
2003-01-16 22:49:22 +00:00
|
|
|
/* 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(Files[i]).CompareNoCase(FontName))
|
|
|
|
|
continue;
|
2003-01-10 02:02:51 +00:00
|
|
|
|
2003-01-16 22:49:22 +00:00
|
|
|
/* If it's an INI, and we don't already have an INI, use it. */
|
|
|
|
|
if(!Files[i].Right(4).CompareNoCase(".ini"))
|
|
|
|
|
{
|
|
|
|
|
if(!IniPath.empty())
|
2003-04-25 00:01:35 +00:00
|
|
|
RageException::Throw("More than one INI found\n%s\n%s", IniPath.c_str(), Files[i].c_str());
|
2003-01-16 22:49:22 +00:00
|
|
|
|
2003-01-23 04:31:14 +00:00
|
|
|
IniPath = sDir+Files[i];
|
2003-01-16 22:49:22 +00:00
|
|
|
continue;
|
|
|
|
|
}
|
2003-01-10 02:02:51 +00:00
|
|
|
|
2003-01-23 04:31:14 +00:00
|
|
|
TexturePaths.push_back(sDir+Files[i]);
|
2003-01-16 22:49:22 +00:00
|
|
|
}
|
2003-01-10 02:02:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CString Font::GetPageNameFromFileName(const CString &fn)
|
|
|
|
|
{
|
|
|
|
|
unsigned begin = fn.find_first_of('[');
|
|
|
|
|
if(begin == fn.npos) return "main";
|
|
|
|
|
unsigned end = fn.find_first_of(']', begin);
|
|
|
|
|
if(end == fn.npos) return "main";
|
|
|
|
|
begin++; end--;
|
|
|
|
|
if(end == begin) return "main";
|
|
|
|
|
return fn.substr(begin, end-begin+1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Font::LoadFontPageSettings(FontPageSettings &cfg, IniFile &ini, const CString &TexturePath, const CString &PageName, CString sChars)
|
|
|
|
|
{
|
|
|
|
|
cfg.TexturePath = TexturePath;
|
|
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
}
|
2003-02-11 19:56:13 +00:00
|
|
|
int NumFramesWide, NumFramesHigh;
|
|
|
|
|
RageTexture::GetFrameDimensionsFromFileName(TexturePath, &NumFramesWide, &NumFramesHigh);
|
|
|
|
|
int NumFrames = NumFramesWide * NumFramesHigh;
|
2003-01-10 02:02:51 +00:00
|
|
|
|
|
|
|
|
ini.RenameKey("Char Widths", "main");
|
|
|
|
|
|
2003-01-24 01:35:05 +00:00
|
|
|
// LOG->Trace("Loading font page '%s' settings from page name '%s'",
|
2003-04-25 00:01:35 +00:00
|
|
|
// TexturePath.c_str(), PageName.c_str());
|
2003-01-10 02:02:51 +00:00
|
|
|
|
2003-10-02 02:03:29 +00:00
|
|
|
ini.GetValue( PageName, "DrawExtraPixelsLeft", cfg.DrawExtraPixelsLeft );
|
|
|
|
|
ini.GetValue( PageName, "DrawExtraPixelsRight", cfg.DrawExtraPixelsRight );
|
|
|
|
|
ini.GetValue( PageName, "AddToAllWidths", cfg.AddToAllWidths );
|
|
|
|
|
ini.GetValue( PageName, "ScaleAllWidthsBy", cfg.ScaleAllWidthsBy );
|
|
|
|
|
ini.GetValue( PageName, "LineSpacing", cfg.LineSpacing );
|
|
|
|
|
ini.GetValue( PageName, "Top", cfg.Top );
|
|
|
|
|
ini.GetValue( PageName, "Baseline", cfg.Baseline );
|
|
|
|
|
ini.GetValue( PageName, "DefaultWidth", cfg.DefaultWidth );
|
|
|
|
|
ini.GetValue( PageName, "AdvanceExtraPixels", cfg.AdvanceExtraPixels );
|
2003-10-08 02:54:40 +00:00
|
|
|
ini.GetValue( PageName, "TextureHints", cfg.TextureHints );
|
2003-01-10 02:02:51 +00:00
|
|
|
|
|
|
|
|
/* Iterate over all keys. */
|
|
|
|
|
const IniFile::key *k = ini.GetKey(PageName);
|
2004-05-31 00:11:34 +00:00
|
|
|
if( k )
|
2003-01-10 02:02:51 +00:00
|
|
|
{
|
2004-05-31 00:11:34 +00:00
|
|
|
for(IniFile::key::const_iterator key = k->begin(); key != k->end(); ++key)
|
2003-01-10 02:02:51 +00:00
|
|
|
{
|
2004-05-31 00:11:34 +00:00
|
|
|
CString val = key->first;
|
|
|
|
|
CString data = key->second;
|
2003-01-10 02:02:51 +00:00
|
|
|
|
2004-05-31 00:11:34 +00:00
|
|
|
val.MakeUpper();
|
|
|
|
|
|
|
|
|
|
/* If val is an integer, it's a width, eg. "10=27". */
|
|
|
|
|
if(IsAnInt(val))
|
2003-01-10 02:02:51 +00:00
|
|
|
{
|
2004-05-31 00:11:34 +00:00
|
|
|
cfg.GlyphWidths[atoi(val)] = atoi(data);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2003-01-10 02:02:51 +00:00
|
|
|
|
2004-05-31 00:11:34 +00:00
|
|
|
/* "map codepoint=frame" maps a char to a frame. */
|
|
|
|
|
if(val.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 codepoint = val.substr(4); /* "CODEPOINT" */
|
|
|
|
|
|
|
|
|
|
Game game = GAME_INVALID;
|
2003-01-10 02:02:51 +00:00
|
|
|
|
2004-05-31 00:11:34 +00:00
|
|
|
if(codepoint.find_first_of(' ') != codepoint.npos)
|
2003-01-17 20:44:06 +00:00
|
|
|
{
|
2004-05-31 00:11:34 +00:00
|
|
|
/* There's a space; the first word should be a game type. Split it. */
|
|
|
|
|
unsigned pos = codepoint.find_first_of(' ');
|
|
|
|
|
CString gamename = codepoint.substr(0, pos);
|
|
|
|
|
codepoint = codepoint.substr(pos+1);
|
|
|
|
|
|
|
|
|
|
game = GameManager::StringToGameType(gamename);
|
|
|
|
|
|
|
|
|
|
if(game == GAME_INVALID)
|
|
|
|
|
{
|
|
|
|
|
LOG->Warn( "Font definition '%s' uses unknown game type '%s'",
|
|
|
|
|
ini.GetPath().c_str(), gamename.c_str() );
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2003-01-17 20:44:06 +00:00
|
|
|
}
|
2003-01-10 02:02:51 +00:00
|
|
|
|
2004-05-31 00:11:34 +00:00
|
|
|
wchar_t c;
|
|
|
|
|
if(codepoint.substr(0, 2) == "U+" && IsHexVal(codepoint.substr(2)))
|
|
|
|
|
sscanf(codepoint.substr(2).c_str(), "%x", &c);
|
|
|
|
|
else if(codepoint.size() > 0 &&
|
|
|
|
|
utf8_get_char_len(codepoint[0]) == int(codepoint.size()))
|
|
|
|
|
{
|
|
|
|
|
c = utf8_get_char(codepoint.c_str());
|
|
|
|
|
if(c == wchar_t(-1))
|
|
|
|
|
LOG->Warn("Font definition '%s' has an invalid value '%s'.",
|
|
|
|
|
ini.GetPath().c_str(), val.c_str() );
|
|
|
|
|
}
|
|
|
|
|
else if(!FontCharAliases::GetChar(codepoint, c))
|
|
|
|
|
{
|
2003-02-11 07:10:22 +00:00
|
|
|
LOG->Warn("Font definition '%s' has an invalid value '%s'.",
|
2003-04-25 00:01:35 +00:00
|
|
|
ini.GetPath().c_str(), val.c_str() );
|
2004-05-31 00:11:34 +00:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(game != GAME_INVALID)
|
|
|
|
|
{
|
|
|
|
|
longchar lc = FontManager::MakeGameGlyph(c, game);
|
|
|
|
|
cfg.CharToGlyphNo[lc] = atoi(data);
|
|
|
|
|
} else {
|
|
|
|
|
cfg.CharToGlyphNo[c] = atoi(data);
|
|
|
|
|
}
|
|
|
|
|
|
2003-01-28 03:40:28 +00:00
|
|
|
continue;
|
2003-01-17 20:44:06 +00:00
|
|
|
}
|
2003-01-10 02:02:51 +00:00
|
|
|
|
2004-05-31 00:11:34 +00:00
|
|
|
if(val.substr(0, 6) == "RANGE ")
|
2003-01-28 03:40:28 +00:00
|
|
|
{
|
2004-05-31 00:11:34 +00:00
|
|
|
/* 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<CString> matches;
|
|
|
|
|
static Regex parse("^RANGE ([A-Z\\-]+)( ?#([0-9A-F]+)-([0-9A-F]+))?$");
|
|
|
|
|
bool match = parse.Compare(val, matches);
|
|
|
|
|
|
|
|
|
|
ASSERT(matches.size() == 4); /* 4 parens */
|
|
|
|
|
|
|
|
|
|
if(!match || matches[0].empty())
|
|
|
|
|
RageException::Throw("Font definition '%s' has an invalid range '%s': parse error",
|
|
|
|
|
ini.GetPath().c_str(), val.c_str() );
|
|
|
|
|
|
|
|
|
|
/* We must have either 1 match (just the codeset) or 4 (the whole thing). */
|
2003-01-10 02:02:51 +00:00
|
|
|
|
2004-05-31 00:11:34 +00:00
|
|
|
int cnt = -1;
|
|
|
|
|
int first = 0;
|
|
|
|
|
if(!matches[2].empty())
|
|
|
|
|
{
|
|
|
|
|
sscanf(matches[2].c_str(), "%x", &first);
|
|
|
|
|
int last;
|
|
|
|
|
sscanf(matches[3].c_str(), "%x", &last);
|
|
|
|
|
if(last < first)
|
|
|
|
|
RageException::Throw("Font definition '%s' has an invalid range '%s': %i < %i.",
|
|
|
|
|
ini.GetPath().c_str(), val.c_str(), last < first );
|
|
|
|
|
|
|
|
|
|
cnt = last-first+1;
|
|
|
|
|
}
|
2003-01-16 20:21:07 +00:00
|
|
|
|
2004-05-31 00:11:34 +00:00
|
|
|
CString ret = cfg.MapRange(matches[0], first, atoi(data), cnt);
|
|
|
|
|
if(!ret.empty())
|
|
|
|
|
RageException::Throw("Font definition '%s' has an invalid range '%s': %s.",
|
|
|
|
|
ini.GetPath().c_str(), val.c_str(), ret.c_str() );
|
2003-01-10 02:02:51 +00:00
|
|
|
|
2004-05-31 00:11:34 +00:00
|
|
|
continue;
|
|
|
|
|
}
|
2003-01-10 02:02:51 +00:00
|
|
|
|
2004-05-31 00:11:34 +00:00
|
|
|
if(val.substr(0, 5) == "LINE ")
|
2003-01-16 20:21:07 +00:00
|
|
|
{
|
2004-05-31 00:11:34 +00:00
|
|
|
/* line ROW=CHAR1CHAR2CHAR3CHAR4
|
|
|
|
|
* eg.
|
|
|
|
|
* line 0=ABCDEFGH
|
|
|
|
|
*
|
|
|
|
|
* This lets us assign characters very compactly and readably. */
|
|
|
|
|
|
|
|
|
|
CString row_str = val.substr(5);
|
|
|
|
|
ASSERT(IsAnInt(row_str));
|
|
|
|
|
const int row = atoi(row_str.c_str());
|
|
|
|
|
const int first_frame = row * NumFramesWide;
|
|
|
|
|
|
|
|
|
|
if(row > NumFramesHigh)
|
|
|
|
|
RageException::Throw("The font definition \"%s\" tries to assign line %i, but the font is only %i characters high",
|
|
|
|
|
ini.GetPath().c_str(), first_frame, NumFramesHigh);
|
|
|
|
|
|
|
|
|
|
/* Decode the string. */
|
|
|
|
|
const wstring wdata(CStringToWstring(data));
|
|
|
|
|
|
|
|
|
|
if(int(wdata.size()) > NumFramesWide)
|
|
|
|
|
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(), row, wdata.c_str(), NumFramesWide);
|
|
|
|
|
|
|
|
|
|
for(unsigned i = 0; i < wdata.size(); ++i)
|
|
|
|
|
cfg.CharToGlyphNo[wdata[i]] = first_frame+i;
|
2003-01-16 20:21:07 +00:00
|
|
|
}
|
2003-02-11 19:56:13 +00:00
|
|
|
}
|
2003-01-10 02:02:51 +00:00
|
|
|
}
|
2003-01-16 20:21:07 +00:00
|
|
|
|
|
|
|
|
/* 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(cfg.CharToGlyphNo.empty() && NumFrames == 128)
|
|
|
|
|
cfg.MapRange("ascii", 0, 0, -1);
|
|
|
|
|
else if(cfg.CharToGlyphNo.empty() && NumFrames == 256)
|
|
|
|
|
cfg.MapRange("cp1252", 0, 0, -1);
|
2004-05-31 00:11:34 +00:00
|
|
|
else if(cfg.CharToGlyphNo.empty() && NumFrames == 15)
|
|
|
|
|
cfg.MapRange("numbers", 0, 0, -1);
|
2004-01-10 03:32:46 +00:00
|
|
|
|
|
|
|
|
/* If ' ' is set and nbsp is not, set nbsp. */
|
|
|
|
|
if( cfg.CharToGlyphNo.find(' ') != cfg.CharToGlyphNo.end() )
|
|
|
|
|
cfg.CharToGlyphNo[0x00A0] = cfg.CharToGlyphNo[' '];
|
2003-01-16 20:21:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CString FontPageSettings::MapRange(CString Mapping, int map_offset, int glyphno, int cnt)
|
|
|
|
|
{
|
|
|
|
|
if(!Mapping.CompareNoCase("Unicode"))
|
|
|
|
|
{
|
|
|
|
|
/* Special case. */
|
|
|
|
|
if(cnt == -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(cnt > 16384)
|
|
|
|
|
return ssprintf("Can't map %i glyphs to one font page", cnt);
|
|
|
|
|
|
|
|
|
|
while(cnt)
|
|
|
|
|
{
|
|
|
|
|
CharToGlyphNo[map_offset] = glyphno;
|
|
|
|
|
map_offset++;
|
|
|
|
|
glyphno++;
|
|
|
|
|
cnt--;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const wchar_t *mapping = FontCharmaps::get_char_map(Mapping);
|
|
|
|
|
if(mapping == NULL)
|
|
|
|
|
return "Unknown mapping";
|
|
|
|
|
|
|
|
|
|
while(*mapping != 0 && map_offset) { mapping++; map_offset--; }
|
|
|
|
|
if(map_offset)
|
|
|
|
|
return "Map overflow"; /* there aren't enough characters in the map */
|
|
|
|
|
|
|
|
|
|
/* If cnt is -1, set it to the number of characters in the map. */
|
|
|
|
|
if(cnt == -1)
|
|
|
|
|
for(cnt = 0; mapping[cnt] != 0; ++cnt) ;
|
|
|
|
|
|
|
|
|
|
while(*mapping != 0)
|
|
|
|
|
{
|
|
|
|
|
if(*mapping != FontCharmaps::M_SKIP)
|
|
|
|
|
CharToGlyphNo[*mapping] = glyphno;
|
|
|
|
|
mapping++;
|
|
|
|
|
glyphno++;
|
|
|
|
|
cnt--;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(cnt)
|
|
|
|
|
return "Map overflow"; /* there aren't enough characters in the map */
|
|
|
|
|
|
|
|
|
|
return "";
|
2003-01-10 02:02:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
2003-04-25 00:01:35 +00:00
|
|
|
RageException::Throw("Font import recursion detected\n%s", str.c_str());
|
2003-01-10 02:02:51 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
LoadStack.push_back(sFontOrTextureFilePath);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* The font is not already loaded. Figure out what we have. */
|
2003-10-22 06:37:31 +00:00
|
|
|
CHECKPOINT_M( ssprintf("Font::Load(\"%s\",\"%s\").", sFontOrTextureFilePath.c_str(), Chars.c_str()) );
|
2003-01-10 02:02:51 +00:00
|
|
|
|
|
|
|
|
path = sFontOrTextureFilePath;
|
|
|
|
|
Chars = sChars;
|
|
|
|
|
|
|
|
|
|
/* Get the filenames associated with this font. */
|
|
|
|
|
CStringArray TexturePaths;
|
|
|
|
|
CString IniPath;
|
|
|
|
|
GetFontPaths(sFontOrTextureFilePath, TexturePaths, IniPath);
|
2003-01-16 22:49:22 +00:00
|
|
|
|
|
|
|
|
/* If we don't have at least one INI or at least one texture path,
|
|
|
|
|
* we have nothing at all. */
|
|
|
|
|
ASSERT(!IniPath.empty() || TexturePaths.size());
|
|
|
|
|
|
2003-01-10 02:02:51 +00:00
|
|
|
bool CapitalsOnly = false;
|
|
|
|
|
|
|
|
|
|
/* If we have an INI, load it. */
|
|
|
|
|
IniFile ini;
|
|
|
|
|
if( !IniPath.empty() )
|
|
|
|
|
{
|
2004-05-23 02:27:51 +00:00
|
|
|
ini.ReadFile( IniPath );
|
2003-01-10 02:02:51 +00:00
|
|
|
ini.RenameKey("Char Widths", "main");
|
2003-10-02 02:03:29 +00:00
|
|
|
ini.GetValue( "main", "CapitalsOnly", CapitalsOnly );
|
2003-01-10 02:02:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
/* If this is a top-level font (not a subfont), load the default font first. */
|
|
|
|
|
CStringArray ImportList;
|
|
|
|
|
if(LoadStack.size() == 1)
|
2003-03-30 18:12:57 +00:00
|
|
|
ImportList.push_back("Common default");
|
2003-01-10 02:02:51 +00:00
|
|
|
|
|
|
|
|
/* 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)
|
|
|
|
|
{
|
2003-10-17 23:41:16 +00:00
|
|
|
CString path = THEME->GetPathToF( 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;
|
|
|
|
|
}
|
|
|
|
|
|
2003-01-10 02:02:51 +00:00
|
|
|
Font subfont;
|
|
|
|
|
subfont.Load(path, "");
|
|
|
|
|
MergeFont(subfont);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Load each font page. */
|
|
|
|
|
for(unsigned i = 0; i < TexturePaths.size(); ++i)
|
|
|
|
|
{
|
2004-05-31 00:11:34 +00:00
|
|
|
const CString sTexturePath = TexturePaths[i];
|
|
|
|
|
|
2003-01-10 02:02:51 +00:00
|
|
|
FontPage *fp = new FontPage;
|
|
|
|
|
|
|
|
|
|
/* Grab the page name, eg "foo" from "Normal [foo].png". */
|
2004-05-31 00:11:34 +00:00
|
|
|
CString pagename = GetPageNameFromFileName(sTexturePath);
|
2003-01-10 02:02:51 +00:00
|
|
|
|
|
|
|
|
/* Load settings for this page from the INI. */
|
|
|
|
|
FontPageSettings cfg;
|
2003-10-08 23:18:56 +00:00
|
|
|
LoadFontPageSettings(cfg, ini, TexturePaths[i], "common", sChars);
|
2003-01-10 02:02:51 +00:00
|
|
|
LoadFontPageSettings(cfg, ini, TexturePaths[i], pagename, sChars);
|
|
|
|
|
|
|
|
|
|
/* Go. */
|
|
|
|
|
fp->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<longchar,int>::const_iterator it = fp->m_iCharToGlyphNo.begin();
|
|
|
|
|
it != fp->m_iCharToGlyphNo.end(); ++it)
|
|
|
|
|
{
|
|
|
|
|
if(it->second < fp->m_pTexture->GetNumFrames()) continue; /* OK */
|
|
|
|
|
RageException::Throw( "The font '%s' maps %s to frame %i, but the font only has %i frames.",
|
2003-04-25 00:01:35 +00:00
|
|
|
TexturePaths[i].c_str(), WcharDisplayText(wchar_t(it->first)).c_str(), it->second, fp->m_pTexture->GetNumFrames() );
|
2003-01-10 02:02:51 +00:00
|
|
|
}
|
|
|
|
|
|
2003-01-24 01:35:05 +00:00
|
|
|
// LOG->Trace("Adding page %s (%s) to %s; %i glyphs",
|
2003-04-25 00:01:35 +00:00
|
|
|
// TexturePaths[i].c_str(), pagename.c_str(),
|
|
|
|
|
// sFontOrTextureFilePath.c_str(), fp->m_iCharToGlyphNo.size());
|
2003-01-10 02:02:51 +00:00
|
|
|
AddPage(fp);
|
|
|
|
|
|
|
|
|
|
/* If this is the first font loaded, or it's called "main", this page's
|
|
|
|
|
* properties become the font's properties. */
|
|
|
|
|
if(i == 0 || pagename == "main")
|
|
|
|
|
SetDefaultGlyph(fp);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(CapitalsOnly)
|
|
|
|
|
CapsOnly();
|
|
|
|
|
|
|
|
|
|
if(m_iCharToGlyph.empty())
|
2003-04-25 00:01:35 +00:00
|
|
|
LOG->Warn("Font %s has no characters", sFontOrTextureFilePath.c_str());
|
2003-01-10 02:02:51 +00:00
|
|
|
|
|
|
|
|
LoadStack.pop_back();
|
|
|
|
|
}
|