jedioutcast/CODE-mp/renderer/tr_font.cpp

721 lines
20 KiB
C++
Raw Normal View History

2013-04-04 14:52:42 +00:00
// tr_font.c
//
//
#include "tr_local.h"
//#include "../qcommon/qcommon.h"
#include "../qcommon/sstring.h" // stl string class won't compile in here (MS shite), so use Gil's.
#include "tr_local.h"
#include "tr_font.h"
#pragma warning (push, 3) //go back down to 3 for the stl include
#include <vector>
#include <map>
//#include <list>
//#include <string>
#pragma warning (pop)
using namespace std;
inline int Round(float value)
{
return((int)floorf(value + 0.5f));
}
int fontIndex; // entry 0 is reserved index for missing/invalid, else ++ with each new font registered
vector<CFontInfo *> fontArray;
typedef map<sstring_t, int> fontIndexMap_t;
fontIndexMap_t fontIndexMap;
//paletteRGBA_c lastcolour;
// =============================== some korean stuff =======================================
#define KSC5601_HANGUL_HIBYTE_START 0xB0 // range is...
#define KSC5601_HANGUL_HIBYTE_STOP 0xC8 // ... inclusive
#define KSC5601_HANGUL_LOBYTE_LOBOUND 0xA0 // range is...
#define KSC5601_HANGUL_LOBYTE_HIBOUND 0xFF // ...bounding (ie only valid in between these points, but NULLs in charsets for these codes)
#define KSC5601_HANGUL_CODES_PER_ROW 96 // 2 more than the number of glyphs
//extern qboolean Language_IsKorean( void );
qboolean Language_IsKorean( void )
{
return qfalse; // multiplayer does not support asian languages
}
static inline bool Korean_ValidKSC5601Hangul( byte _iHi, byte _iLo )
{
return (_iHi >=KSC5601_HANGUL_HIBYTE_START &&
_iHi <=KSC5601_HANGUL_HIBYTE_STOP &&
_iLo > KSC5601_HANGUL_LOBYTE_LOBOUND &&
_iLo < KSC5601_HANGUL_LOBYTE_HIBOUND
);
}
static inline bool Korean_ValidKSC5601Hangul( unsigned int uiCode )
{
return Korean_ValidKSC5601Hangul( uiCode >> 8, uiCode & 0xFF );
}
// takes a KSC5601 double-byte hangul code and collapses down to a 0..n glyph index...
// Assumes rows are 96 wide (glyph slots), not 94 wide (actual glyphs), so I can ignore boundary markers
//
// (invalid hangul codes will return 0)
//
static int Korean_CollapseKSC5601HangulCode(unsigned int uiCode)
{
if (Korean_ValidKSC5601Hangul( uiCode ))
{
uiCode -= (KSC5601_HANGUL_HIBYTE_START * 256) + KSC5601_HANGUL_LOBYTE_LOBOUND; // sneaky maths on both bytes, reduce to 0x0000 onwards
uiCode = ((uiCode >> 8) * KSC5601_HANGUL_CODES_PER_ROW) + (uiCode & 0xFF);
return uiCode;
}
return 0;
}
// ======================== some taiwanese stuff ==============================
// (all ranges inclusive for Big5)...
//
#define BIG5_HIBYTE_START0 0xA1 // (misc chars + level 1 hanzi)
#define BIG5_HIBYTE_STOP0 0xC6 //
#define BIG5_HIBYTE_START1 0xC9 // (level 2 hanzi)
#define BIG5_HIBYTE_STOP1 0xF9 //
#define BIG5_LOBYTE_LOBOUND0 0x40 //
#define BIG5_LOBYTE_HIBOUND0 0x7E //
#define BIG5_LOBYTE_LOBOUND1 0xA1 //
#define BIG5_LOBYTE_HIBOUND1 0xFE //
#define BIG5_CODES_PER_ROW 160 // 3 more than the number of glyphs
//extern qboolean Language_IsTaiwanese( void );
qboolean Language_IsTaiwanese( void )
{
return qfalse; // multiplayer does not support asian languages
}
static bool Taiwanese_ValidBig5Code( unsigned int uiCode )
{
if ( (uiCode >= ((BIG5_HIBYTE_START0<<8)|BIG5_LOBYTE_LOBOUND0) && uiCode <= ((BIG5_HIBYTE_STOP0<<8)|BIG5_LOBYTE_HIBOUND0))
|| (uiCode >= ((BIG5_HIBYTE_START1<<8)|BIG5_LOBYTE_LOBOUND0) && uiCode <= ((BIG5_HIBYTE_STOP1<<8)|0xD5)) // no meaningful equate for this, it's just end-of-glyphs for highest row
)
{
const byte _iLo = uiCode & 0xFF;
if ( (_iLo >= BIG5_LOBYTE_LOBOUND0 && _iLo <= BIG5_LOBYTE_HIBOUND0) ||
(_iLo >= BIG5_LOBYTE_LOBOUND1 && _iLo <= BIG5_LOBYTE_HIBOUND1)
)
{
return true;
}
}
return false;
}
// takes a BIG5 double-byte code (including level 2 hanzi) and collapses down to a 0..n glyph index...
// Assumes rows are 160 wide (glyph slots), not 157 wide (actual glyphs), so I can ignore boundary markers
//
// (invalid big5 codes will return 0)
//
static int Taiwanese_CollapseBig5Code( unsigned int uiCode )
{
if (Taiwanese_ValidBig5Code( uiCode ))
{
uiCode -= (BIG5_HIBYTE_START0 * 256) + BIG5_LOBYTE_LOBOUND0; // sneaky maths on both bytes, reduce to 0x0000 onwards
if ( (uiCode & 0xFF) >= (BIG5_LOBYTE_LOBOUND1-1)-BIG5_LOBYTE_LOBOUND0)
{
uiCode -= ((BIG5_LOBYTE_LOBOUND1-1) - (BIG5_LOBYTE_HIBOUND0+1)) -1;
}
uiCode = ((uiCode >> 8) * BIG5_CODES_PER_ROW) + (uiCode & 0xFF);
return uiCode;
}
return 0;
}
// ============================================================================
// takes char *, returns integer char at that point, and advances char * on by enough bytes to move
// past the letter (either western 1 byte or Asian multi-byte)...
//
unsigned int AnyLanguage_ReadCharFromString( const char **ppsText )
{
const byte *psString = (const byte *) *ppsText; // avoid sign-promote bug
unsigned int uiLetter;
// at some stage I can put other MBCS languages here I guess, but for now...
//
if ( (Language_IsKorean() && Korean_ValidKSC5601Hangul( psString[0], psString[1] )) ||
(Language_IsTaiwanese()&& Taiwanese_ValidBig5Code ((psString[0] * 256) + psString[1]))
)
{
uiLetter = (psString[0] * 256) + psString[1];
*ppsText += 2;
}
else
{
uiLetter = psString[0];
*ppsText += 1; // NOT ++
}
return uiLetter;
}
// ======================================================================
CFontInfo::CFontInfo(const char *fontName)
{
int len, i;
void *buff;
dfontdat_t *fontdat;
len = ri.FS_ReadFile(fontName, NULL);
if (len == sizeof(dfontdat_t))
{
ri.FS_ReadFile(fontName, &buff);
fontdat = (dfontdat_t *)buff;
for(i = 0; i < GLYPH_COUNT; i++)
{
mGlyphs[i] = fontdat->mGlyphs[i];
}
mPointSize = fontdat->mPointSize;
mHeight = fontdat->mHeight;
mAscender = fontdat->mAscender;
mDescender = fontdat->mDescender;
mAsianHack = fontdat->mKoreanHack;
mbRoundCalcs = !!strstr(fontName,"ergo");
ri.FS_FreeFile(buff);
}
else
{
mHeight = 0;
mShader = 0;
}
Q_strncpyz(m_sFontName, fontName, sizeof(m_sFontName));
COM_StripExtension( m_sFontName, m_sFontName ); // so we get better error printing if failed to load shader (ie lose ".fontdat")
mShader = RE_RegisterShaderNoMip(m_sFontName);
FlagNoAsianGlyphs();
UpdateAsianIfNeeded(true);
// finished...
fontArray.resize(fontIndex + 1);
fontArray[fontIndex++] = this;
}
//extern int Language_GetIntegerValue(void);
static int Language_GetIntegerValue(void)
{
return 0; // irrlevant in this codebase since multiplayer doesn't support asian chars
}
void CFontInfo::UpdateAsianIfNeeded( bool bForceReEval /* = false */ )
{
// if asian language, then provide an alternative glyph set and fill in relevant fields...
//
if (mHeight) // western charset exists in first place?
{
qboolean bKorean = Language_IsKorean();
qboolean bTaiwanese = Language_IsTaiwanese();
if (bKorean || bTaiwanese)
{
const int iThisLanguage = Language_GetIntegerValue();
int iCappedHeight = mHeight < 16 ? 16: mHeight; // arbitrary limit on small char sizes because Asian chars don't squash well
if (m_iAsianLanguageLoaded != iThisLanguage || !AsianGlyphsAvailable() || bForceReEval)
{
m_iAsianLanguageLoaded = iThisLanguage;
int iGlyphTPs = 0;
const char *psLang = NULL;
if (bKorean)
{
iGlyphTPs = GLYPH_MAX_KOREAN_SHADERS;
psLang = "kor";
m_iAsianGlyphsAcross = 32; // hardwired for now (only one glyph set), may change later
}
else
if (bTaiwanese)
{
iGlyphTPs = GLYPH_MAX_TAIWANESE_SHADERS;
psLang = "tai";
m_iAsianGlyphsAcross = 64; // hardwired for now (only one glyph set), may change later
}
// textures need loading...
//
if (m_sFontName[0])
{
// Use this sometime if we need to do logic to load alternate-height glyphs to better fit other fonts.
// (but for now, we just use the one glyph set)
//
}
for (int i = 0; i < iGlyphTPs; i++)
{
// (Note!! assumption for S,T calculations: all Asian glyph textures pages are square except for last one)
//
char sTemp[MAX_QPATH];
Com_sprintf(sTemp,sizeof(sTemp), "fonts/%s_%d_1024_%d", psLang, 1024/m_iAsianGlyphsAcross, i);
//
// returning 0 here will automatically inhibit Asian glyph calculations at runtime...
//
m_hAsianShaders[i] = RE_RegisterShaderNoMip( sTemp );
}
// for now I'm hardwiring these, but if we ever have more than one glyph set per language then they'll be changed...
//
m_iAsianPagesLoaded = iGlyphTPs; // not necessarily true, but will be safe, and show up obvious if something missing
m_bAsianLastPageHalfHeight = true;
bForceReEval = true;
}
if (bForceReEval)
{
// now init the Asian member glyph fields to make them come out the same size as the western ones
// that they serve as an alternative for...
//
m_AsianGlyph.width = iCappedHeight; // square Asian chars same size as height of western set
m_AsianGlyph.height = iCappedHeight; // ""
m_AsianGlyph.horizAdvance = iCappedHeight + (bTaiwanese?3:-1); // Asian chars contain a small amount of space in the glyph
m_AsianGlyph.horizOffset = 0; // ""
// .. or you can use the mKoreanHack value (which is the same number as the calc below)
m_AsianGlyph.baseline = mAscender + ((iCappedHeight - mHeight) >> 1);
}
}
else
{
// not using Asian...
//
FlagNoAsianGlyphs();
}
}
else
{
// no western glyphs available, so don't attempt to match asian...
//
FlagNoAsianGlyphs();
}
}
// needed to add *piShader param because of multiple TPs,
// if not passed in, then I also skip S,T calculations for re-usable static korean glyphinfo struct...
//
const glyphInfo_t *CFontInfo::GetLetter(const unsigned int uiLetter, int *piShader /* = NULL */)
{
if ( AsianGlyphsAvailable() )
{
int iCollapsedAsianCode = 0;
qboolean bTaiHack = qfalse;
if ( Language_IsKorean() )
{
iCollapsedAsianCode = Korean_CollapseKSC5601HangulCode( uiLetter );
}
else
if ( Language_IsTaiwanese() )
{
iCollapsedAsianCode = Taiwanese_CollapseBig5Code( uiLetter );
bTaiHack = qtrue;
}
if (iCollapsedAsianCode)
{
if (piShader)
{
// (Note!! assumption for S,T calculations: all asian glyph textures pages are square except for last one
// which may or may not be half height)
//
int iTexturePageIndex = iCollapsedAsianCode / (m_iAsianGlyphsAcross * m_iAsianGlyphsAcross);
if (iTexturePageIndex > m_iAsianPagesLoaded)
{
assert(0); // should never happen
iTexturePageIndex = 0;
}
iCollapsedAsianCode -= iTexturePageIndex * (m_iAsianGlyphsAcross * m_iAsianGlyphsAcross);
const int iColumn = iCollapsedAsianCode % m_iAsianGlyphsAcross;
const int iRow = iCollapsedAsianCode / m_iAsianGlyphsAcross;
const bool bHalfT = (iTexturePageIndex == (m_iAsianPagesLoaded - 1) && m_bAsianLastPageHalfHeight);
const int iAsianGlyphsDown = (bHalfT) ? m_iAsianGlyphsAcross / 2 : m_iAsianGlyphsAcross;
if (!bTaiHack)
{
// standard...
//
m_AsianGlyph.s = (float)( iColumn ) / (float)m_iAsianGlyphsAcross;
m_AsianGlyph.t = (float)( iRow ) / (float) iAsianGlyphsDown;
}
else
{
// special hack for Taiwanese stuff to avoid GL-bleed...
//
m_AsianGlyph.s = (float)(((1024 / m_iAsianGlyphsAcross) * ( iColumn ))+1) / 1024.0f;
m_AsianGlyph.t = (float)(((1024 / iAsianGlyphsDown ) * ( iRow ))+1) / 1024.0f;
}
m_AsianGlyph.s2 = (float)( iColumn + 1) / (float)m_iAsianGlyphsAcross;
m_AsianGlyph.t2 = (float)( iRow + 1 ) / (float) iAsianGlyphsDown;
*piShader = m_hAsianShaders[ iTexturePageIndex ];
}
return &m_AsianGlyph;
}
}
if (piShader)
{
*piShader = GetShader();
}
return(mGlyphs + (uiLetter & 0xff));
}
const int CFontInfo::GetAsianCode(ulong uiLetter) const
{
int iCollapsedAsianCode = 0;
if (AsianGlyphsAvailable())
{
if ( Language_IsKorean() )
{
iCollapsedAsianCode = Korean_CollapseKSC5601HangulCode( uiLetter );
}
else
if ( Language_IsTaiwanese() )
{
iCollapsedAsianCode = Taiwanese_CollapseBig5Code( uiLetter );
}
}
return iCollapsedAsianCode;
}
const int CFontInfo::GetLetterWidth(unsigned int uiLetter) const
{
if ( GetAsianCode(uiLetter) )
{
return m_AsianGlyph.width;
}
uiLetter &= 0xff;
if(mGlyphs[uiLetter].width)
{
return(mGlyphs[uiLetter].width);
}
return(mGlyphs['.'].width);
}
const int CFontInfo::GetLetterHorizAdvance(unsigned int uiLetter) const
{
if ( GetAsianCode(uiLetter) )
{
return m_AsianGlyph.horizAdvance;
}
uiLetter &= 0xff;
if(mGlyphs[uiLetter].horizAdvance)
{
return(mGlyphs[uiLetter].horizAdvance);
}
return(mGlyphs['.'].horizAdvance);
}
CFontInfo *GetFont(int index)
{
index &= SET_MASK;
if((index >= 1) && (index < fontIndex))
{
CFontInfo *pFont = fontArray[index];
if (pFont)
{
pFont->UpdateAsianIfNeeded();
}
return pFont;
}
return(NULL);
}
int RE_Font_StrLenPixels(const char *psText, const int iFontHandle, const float fScale)
{
int x = 0, i = 0, r = 0;
CFontInfo *curfont;
char parseText[2048];
//It gets confused about ^blah here too and reports an inaccurate length as a result
while (psText[i] && r < 2048)
{
if (psText[i] == '^')
{
if ( (i < 1 || psText[i-1] != '^') &&
(!psText[i+1] || psText[i+1] != '^') )
{ //If char before or after ^ is ^ then it prints ^ instead of accepting a colorcode
i += 2;
}
}
parseText[r] = psText[i];
r++;
i++;
}
parseText[r] = 0;
const char *constParseText = parseText;
curfont = GetFont(iFontHandle);
if(!curfont)
{
return(0);
}
while(*constParseText)
{
2013-04-04 18:24:26 +00:00
int a = curfont->GetLetterHorizAdvance( AnyLanguage_ReadCharFromString( &constParseText ));
x += Round ( a * fScale );
2013-04-04 14:52:42 +00:00
}
2013-04-04 18:24:26 +00:00
2013-04-04 14:52:42 +00:00
return(x);
}
// not really a font function, but keeps naming consistant...
//
int RE_Font_StrLenChars(const char *psText)
{
// logic for this function's letter counting must be kept same in this function and RE_Font_DrawString()
//
int iCharCount = 0;
while ( *psText )
{
// in other words, colour codes and CR/LF don't count as chars, all else does...
//
switch (AnyLanguage_ReadCharFromString( &psText ))
{
case '^': psText++; break; // colour code (note next-char skip)
case 10: break; // linefeed
case 13: break; // return
default: iCharCount++; break;
}
}
return iCharCount;
}
int RE_Font_HeightPixels(const int iFontHandle, const float fScale)
{
CFontInfo *curfont;
curfont = GetFont(iFontHandle);
if(curfont)
{
return(Round(curfont->GetPointSize() * fScale));
}
return(0);
}
// iCharLimit is -1 for "all of string", else MBCS char count...
//
void RE_Font_DrawString(int ox, int oy, const char *psText, const float *rgba, const int iFontHandle, int iCharLimit, const float fScale)
{
int x, y, colour, offset;
const glyphInfo_t *pLetter;
qhandle_t hShader;
qboolean qbThisCharCountsAsLetter; // logic for this bool must be kept same in this function and RE_Font_StrLenChars()
if(iFontHandle & STYLE_BLINK)
{
if((ri.Milliseconds() >> 7) & 1)
{
return;
}
}
/* if (Language_IsTaiwanese())
{
psText = "Wp:<3A>}<7D>F<EFBFBD>a <20>p<EFBFBD>G<EFBFBD><47><EFBFBD>A<EFBFBD>Ʊ<EFBFBD><C6B1>A<EFBFBD><41><EFBFBD>L<EFBFBD>̻<EFBFBD><CCBB><EFBFBD><EFBFBD>@<40>˦<EFBFBD><CBA6>C";
}
else
if (Language_IsKorean())
{
psText = "Wp:<3A><>Ÿ<EFBFBD><C5B8><EFBFBD>̴<EFBFBD> <20>ָ<EFBFBD>. <20>׵<EFBFBD><D7B5><EFBFBD> <20><><EFBFBD>Ѵ<EFBFBD><D1B4><EFBFBD> <20>װ<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>ϰڴ<CFB0>.";
}
*/
CFontInfo *curfont = GetFont(iFontHandle);
if(!curfont)
{
return;
}
// Draw a dropshadow if required
if(iFontHandle & STYLE_DROPSHADOW)
{
int i = 0, r = 0;
char dropShadowText[1024];
static const vec4_t v4DKGREY2 = {0.15f, 0.15f, 0.15f, 1};
offset = Round(curfont->GetPointSize() * fScale * 0.075f);
//^blah stuff confuses shadows, so parse it out first
while (psText[i] && r < 1024)
{
if (psText[i] == '^')
{
if ( (i < 1 || psText[i-1] != '^') &&
(!psText[i+1] || psText[i+1] != '^') )
{ //If char before or after ^ is ^ then it prints ^ instead of accepting a colorcode
i += 2;
}
}
dropShadowText[r] = psText[i];
r++;
i++;
}
dropShadowText[r] = 0;
RE_Font_DrawString(ox + offset, oy + offset, dropShadowText, v4DKGREY2, iFontHandle & SET_MASK, iCharLimit, fScale);
}
RE_SetColor( rgba );
x = ox;
oy += Round((curfont->GetHeight() - (curfont->GetDescender() >> 1)) * fScale);
while(*psText)
{
qbThisCharCountsAsLetter = qfalse;
unsigned int uiLetter = AnyLanguage_ReadCharFromString(&psText); // 'psText' ptr has been advanced now
switch( uiLetter )
{
case '^':
{
colour = ColorIndex(*psText++);
RE_SetColor( g_color_table[colour] );
}
break;
case 10: //linefeed
x = ox;
oy += Round(curfont->GetPointSize() * fScale);
break;
case 13: // Return
break;
case 32: // Space
qbThisCharCountsAsLetter = qtrue;
pLetter = curfont->GetLetter(' ');
x += Round(pLetter->horizAdvance * fScale);
break;
default:
qbThisCharCountsAsLetter = qtrue;
pLetter = curfont->GetLetter( uiLetter, &hShader ); // Description of pLetter
if(!pLetter->width)
{
pLetter = curfont->GetLetter('.');
}
// for some reason in JK2MP we DO need these Round() calls, but in SP they cause it to go wrong... ???
//
// this 'mbRoundCalcs' stuff is crap, but the only way to make the font code work. Sigh...
//
y = oy - (curfont->mbRoundCalcs ? Round(pLetter->baseline * fScale) : pLetter->baseline * fScale);
RE_StretchPic ( x + Round(pLetter->horizOffset * fScale), // float x
y, // float y
curfont->mbRoundCalcs ? Round(pLetter->width * fScale) : pLetter->width * fScale, // float w
curfont->mbRoundCalcs ? Round(pLetter->height * fScale) : pLetter->height * fScale, // float h
pLetter->s, // float s1
pLetter->t, // float t1
pLetter->s2, // float s2
pLetter->t2, // float t2
//lastcolour.c,
hShader // qhandle_t hShader
);
x += Round(pLetter->horizAdvance * fScale);
break;
}
if (qbThisCharCountsAsLetter && iCharLimit != -1)
{
if (!--iCharLimit)
break;
}
}
//let it remember the old color //RE_SetColor(NULL);;
}
int RE_RegisterFont(const char *psName)
{
fontIndexMap_t::iterator it = fontIndexMap.find(psName);
if (it != fontIndexMap.end() )
{
int iIndex = (*it).second;
CFontInfo *pFont = GetFont(iIndex);
if (pFont)
{
pFont->UpdateAsianIfNeeded();
}
else
{
return 0; // iIndex will be 0 anyway, but this makes it clear
}
return iIndex;
}
// not registered, so...
//
{
char temp[MAX_QPATH];
Com_sprintf(temp, MAX_QPATH, "fonts/%s.fontdat", psName);
CFontInfo *pFont = new CFontInfo(temp);
if (pFont->GetPointSize() > 0)
{
fontIndexMap[psName] = fontIndex - 1;
return fontIndex - 1;
}
else
{
fontIndexMap[psName] = 0; // missing/invalid
}
}
return(0);
}
void R_InitFonts(void)
{
fontIndex = 1; // entry 0 is reserved for "missing/invalid"
}
void R_ShutdownFonts(void)
{
for(int i = 1; i < fontIndex; i++) // entry 0 is reserved for "missing/invalid"
{
delete fontArray[i];
}
fontIndexMap.clear();
fontArray.clear();
fontIndex = 1; // entry 0 is reserved for "missing/invalid"
}