1715 lines
52 KiB
C++
1715 lines
52 KiB
C++
|
// leave this as first line for PCH reasons...
|
|||
|
//
|
|||
|
#include "../server/exe_headers.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"
|
|||
|
|
|||
|
#include "../qcommon/stringed_ingame.h"
|
|||
|
|
|||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|||
|
//
|
|||
|
// This file is shared in the single and multiplayer codebases, so be CAREFUL WHAT YOU ADD/CHANGE!!!!!
|
|||
|
//
|
|||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|||
|
|
|||
|
typedef enum
|
|||
|
{
|
|||
|
eWestern, // ( I only care about asian languages in here at the moment )
|
|||
|
eRussian, // .. but now I need to care about this, since it uses a different TP
|
|||
|
ePolish, // ditto
|
|||
|
eKorean,
|
|||
|
eTaiwanese, // 15x15 glyphs tucked against BR of 16x16 space
|
|||
|
eJapanese, // 15x15 glyphs tucked against TL of 16x16 space
|
|||
|
eChinese, // 15x15 glyphs tucked against TL of 16x16 space
|
|||
|
eThai, // 16x16 cells with glyphs against left edge, special file (tha_widths.dat) for variable widths
|
|||
|
} Language_e;
|
|||
|
|
|||
|
// this is to cut down on all the stupid string compares I've been doing, and convert asian stuff to switch-case
|
|||
|
//
|
|||
|
Language_e GetLanguageEnum()
|
|||
|
{
|
|||
|
static int iSE_Language_ModificationCount = -1234; // any old silly value that won't match the cvar mod count
|
|||
|
static Language_e eLanguage = eWestern;
|
|||
|
|
|||
|
// only re-strcmp() when language string has changed from what we knew it as...
|
|||
|
//
|
|||
|
if (iSE_Language_ModificationCount != se_language->modificationCount )
|
|||
|
{
|
|||
|
iSE_Language_ModificationCount = se_language->modificationCount;
|
|||
|
|
|||
|
if ( Language_IsRussian() ) eLanguage = eRussian;
|
|||
|
else if ( Language_IsPolish() ) eLanguage = ePolish;
|
|||
|
else if ( Language_IsKorean() ) eLanguage = eKorean;
|
|||
|
else if ( Language_IsTaiwanese() ) eLanguage = eTaiwanese;
|
|||
|
else if ( Language_IsJapanese() ) eLanguage = eJapanese;
|
|||
|
else if ( Language_IsChinese() ) eLanguage = eChinese;
|
|||
|
else if ( Language_IsThai() ) eLanguage = eThai;
|
|||
|
else eLanguage = eWestern;
|
|||
|
}
|
|||
|
|
|||
|
return eLanguage;
|
|||
|
}
|
|||
|
|
|||
|
struct SBCSOverrideLanguages_t
|
|||
|
{
|
|||
|
LPCSTR m_psName;
|
|||
|
Language_e m_eLanguage;
|
|||
|
};
|
|||
|
|
|||
|
// so I can do some stuff with for-next loops when I add polish etc...
|
|||
|
//
|
|||
|
SBCSOverrideLanguages_t g_SBCSOverrideLanguages[]=
|
|||
|
{
|
|||
|
{"russian", eRussian},
|
|||
|
{"polish", ePolish},
|
|||
|
{NULL, eWestern}
|
|||
|
};
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//================================================
|
|||
|
//
|
|||
|
|
|||
|
#define sFILENAME_THAI_WIDTHS "fonts/tha_widths.dat"
|
|||
|
#define sFILENAME_THAI_CODES "fonts/tha_codes.dat"
|
|||
|
|
|||
|
struct ThaiCodes_t
|
|||
|
{
|
|||
|
map <int, int> m_mapValidCodes;
|
|||
|
vector<int> m_viGlyphWidths;
|
|||
|
string m_strInitFailureReason; // so we don't have to keep retrying to work this out
|
|||
|
|
|||
|
void Clear( void )
|
|||
|
{
|
|||
|
m_mapValidCodes.clear();
|
|||
|
m_viGlyphWidths.clear();
|
|||
|
m_strInitFailureReason = ""; // if blank, never failed, else says don't bother re-trying
|
|||
|
}
|
|||
|
|
|||
|
ThaiCodes_t()
|
|||
|
{
|
|||
|
Clear();
|
|||
|
}
|
|||
|
|
|||
|
// convert a supplied 1,2 or 3-byte multiplied-up integer into a valid 0..n index, else -1...
|
|||
|
//
|
|||
|
int GetValidIndex( int iCode )
|
|||
|
{
|
|||
|
map <int,int>::iterator it = m_mapValidCodes.find( iCode );
|
|||
|
if (it != m_mapValidCodes.end())
|
|||
|
{
|
|||
|
return (*it).second;
|
|||
|
}
|
|||
|
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
int GetWidth( int iGlyphIndex )
|
|||
|
{
|
|||
|
if (iGlyphIndex < m_viGlyphWidths.size())
|
|||
|
{
|
|||
|
return m_viGlyphWidths[ iGlyphIndex ];
|
|||
|
}
|
|||
|
|
|||
|
assert(0);
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
// return is error message to display, or NULL for success
|
|||
|
const char *Init(void)
|
|||
|
{
|
|||
|
if (m_mapValidCodes.empty() && m_viGlyphWidths.empty())
|
|||
|
{
|
|||
|
if (m_strInitFailureReason.empty()) // never tried and failed already?
|
|||
|
{
|
|||
|
int *piData = NULL; // note <int>, not <byte>, for []-access
|
|||
|
//
|
|||
|
// read the valid-codes table in...
|
|||
|
//
|
|||
|
int iBytesRead = FS_ReadFile( sFILENAME_THAI_CODES, (void **) &piData );
|
|||
|
if (iBytesRead > 0 && !(iBytesRead&3)) // valid length and multiple of 4 bytes long
|
|||
|
{
|
|||
|
int iTableEntries = iBytesRead / sizeof(int);
|
|||
|
|
|||
|
for (int i=0; i < iTableEntries; i++)
|
|||
|
{
|
|||
|
m_mapValidCodes[ piData[i] ] = i; // convert MBCS code to sequential index...
|
|||
|
}
|
|||
|
FS_FreeFile( piData ); // dispose of original
|
|||
|
|
|||
|
// now read in the widths... (I'll keep these in a simple STL vector, so they'all disappear when the <map> entries do...
|
|||
|
//
|
|||
|
iBytesRead = FS_ReadFile( sFILENAME_THAI_WIDTHS, (void **) &piData );
|
|||
|
if (iBytesRead > 0 && !(iBytesRead&3) && iBytesRead>>2/*sizeof(int)*/ == iTableEntries)
|
|||
|
{
|
|||
|
for (int i=0; i<iTableEntries; i++)
|
|||
|
{
|
|||
|
m_viGlyphWidths.push_back( piData[i] );
|
|||
|
}
|
|||
|
FS_FreeFile( piData ); // dispose of original
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
m_strInitFailureReason = va("Error with file \"%s\", size = %d!\n", sFILENAME_THAI_WIDTHS, iBytesRead);
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
m_strInitFailureReason = va("Error with file \"%s\", size = %d!\n", sFILENAME_THAI_CODES, iBytesRead);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return m_strInitFailureReason.c_str();
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
|
|||
|
#define GLYPH_MAX_KOREAN_SHADERS 3
|
|||
|
#define GLYPH_MAX_TAIWANESE_SHADERS 4
|
|||
|
#define GLYPH_MAX_JAPANESE_SHADERS 3
|
|||
|
#define GLYPH_MAX_CHINESE_SHADERS 3
|
|||
|
#define GLYPH_MAX_THAI_SHADERS 3
|
|||
|
#define GLYPH_MAX_ASIAN_SHADERS 4 // this MUST equal the larger of the above defines
|
|||
|
|
|||
|
class CFontInfo
|
|||
|
{
|
|||
|
private:
|
|||
|
// From the fontdat file
|
|||
|
glyphInfo_t mGlyphs[GLYPH_COUNT];
|
|||
|
|
|||
|
// int mAsianHack; // unused junk from John's fontdat file format.
|
|||
|
// end of fontdat data
|
|||
|
|
|||
|
int mShader; // handle to the shader with the glyph
|
|||
|
|
|||
|
int m_hAsianShaders[GLYPH_MAX_ASIAN_SHADERS]; // shaders for Korean glyphs where applicable
|
|||
|
glyphInfo_t m_AsianGlyph; // special glyph containing asian->western scaling info for all glyphs
|
|||
|
int m_iAsianGlyphsAcross; // needed to dynamically calculate S,T coords
|
|||
|
int m_iAsianPagesLoaded;
|
|||
|
bool m_bAsianLastPageHalfHeight;
|
|||
|
int m_iLanguageModificationCount; // doesn't matter what this is, so long as it's comparable as being changed
|
|||
|
|
|||
|
ThaiCodes_t *m_pThaiData;
|
|||
|
|
|||
|
public:
|
|||
|
char m_sFontName[MAX_QPATH]; // eg "fonts/lcd" // needed for korean font-hint if we need >1 hangul set
|
|||
|
int mPointSize;
|
|||
|
int mHeight;
|
|||
|
int mAscender;
|
|||
|
int mDescender;
|
|||
|
|
|||
|
bool mbRoundCalcs; // trying to make this !@#$%^ thing work with scaling
|
|||
|
int m_iThisFont; // handle to itself
|
|||
|
int m_iAltSBCSFont; // -1 == NULL // alternative single-byte font for languages like russian/polish etc that need to override high characters ?
|
|||
|
int m_iOriginalFontWhenSBCSOverriden;
|
|||
|
float m_fAltSBCSFontScaleFactor; // -1, else amount to adjust returned values by to make them fit the master western font they're substituting for
|
|||
|
bool m_bIsFakeAlienLanguage; // ... if true, don't process as MBCS or override as SBCS etc
|
|||
|
|
|||
|
CFontInfo(const char *fontName);
|
|||
|
// CFontInfo(int fill) { memset(this, fill, sizeof(*this)); } // wtf?
|
|||
|
~CFontInfo(void) {}
|
|||
|
|
|||
|
const int GetPointSize(void) const { return(mPointSize); }
|
|||
|
const int GetHeight(void) const { return(mHeight); }
|
|||
|
const int GetAscender(void) const { return(mAscender); }
|
|||
|
const int GetDescender(void) const { return(mDescender); }
|
|||
|
|
|||
|
const glyphInfo_t *GetLetter(const unsigned int uiLetter, int *piShader = NULL);
|
|||
|
const int GetCollapsedAsianCode(ulong uiLetter) const;
|
|||
|
|
|||
|
const int GetLetterWidth(const unsigned int uiLetter);
|
|||
|
const int GetLetterHorizAdvance(const unsigned int uiLetter);
|
|||
|
const int GetShader(void) const { return(mShader); }
|
|||
|
|
|||
|
void FlagNoAsianGlyphs(void) { m_hAsianShaders[0] = 0; m_iLanguageModificationCount = -1; } // used during constructor
|
|||
|
bool AsianGlyphsAvailable(void) const { return !!(m_hAsianShaders[0]); }
|
|||
|
|
|||
|
void UpdateAsianIfNeeded( bool bForceReEval = false);
|
|||
|
};
|
|||
|
|
|||
|
//================================================
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
// round float to one decimal place...
|
|||
|
//
|
|||
|
float RoundTenth( float fValue )
|
|||
|
{
|
|||
|
return ( floorf( (fValue*10.0f) + 0.5f) ) / 10.0f;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
int g_iCurrentFontIndex; // entry 0 is reserved index for missing/invalid, else ++ with each new font registered
|
|||
|
vector<CFontInfo *> g_vFontArray;
|
|||
|
typedef map<sstring_t, int> FontIndexMap_t;
|
|||
|
FontIndexMap_t g_mapFontIndexes;
|
|||
|
int g_iNonScaledCharRange; // this is used with auto-scaling of asian fonts, anything below this number is preserved in scale, anything above is scaled down by 0.75f
|
|||
|
|
|||
|
//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 );
|
|||
|
|
|||
|
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;
|
|||
|
}
|
|||
|
|
|||
|
static int Korean_InitFields(int &iGlyphTPs, LPCSTR &psLang)
|
|||
|
{
|
|||
|
psLang = "kor";
|
|||
|
iGlyphTPs = GLYPH_MAX_KOREAN_SHADERS;
|
|||
|
g_iNonScaledCharRange = 255;
|
|||
|
return 32; // m_iAsianGlyphsAcross
|
|||
|
}
|
|||
|
|
|||
|
// ======================== 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 );
|
|||
|
|
|||
|
static bool Taiwanese_ValidBig5Code( unsigned int uiCode )
|
|||
|
{
|
|||
|
const byte _iHi = (uiCode >> 8)&0xFF;
|
|||
|
if ( (_iHi >= BIG5_HIBYTE_START0 && _iHi <= BIG5_HIBYTE_STOP0)
|
|||
|
|| (_iHi >= BIG5_HIBYTE_START1 && _iHi <= BIG5_HIBYTE_STOP1)
|
|||
|
)
|
|||
|
{
|
|||
|
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;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
// only call this when Taiwanese_ValidBig5Code() has already returned true...
|
|||
|
//
|
|||
|
static bool Taiwanese_IsTrailingPunctuation( unsigned int uiCode )
|
|||
|
{
|
|||
|
// so far I'm just counting the first 21 chars, those seem to be all the basic punctuation...
|
|||
|
//
|
|||
|
if ( uiCode >= ((BIG5_HIBYTE_START0<<8)|BIG5_LOBYTE_LOBOUND0) &&
|
|||
|
uiCode < ((BIG5_HIBYTE_START0<<8)|BIG5_LOBYTE_LOBOUND0+20)
|
|||
|
)
|
|||
|
{
|
|||
|
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;
|
|||
|
}
|
|||
|
|
|||
|
static int Taiwanese_InitFields(int &iGlyphTPs, LPCSTR &psLang)
|
|||
|
{
|
|||
|
psLang = "tai";
|
|||
|
iGlyphTPs = GLYPH_MAX_TAIWANESE_SHADERS;
|
|||
|
g_iNonScaledCharRange = 255;
|
|||
|
return 64; // m_iAsianGlyphsAcross
|
|||
|
}
|
|||
|
|
|||
|
// ======================== some Japanese stuff ==============================
|
|||
|
|
|||
|
|
|||
|
// ( all ranges inclusive for Shift-JIS )
|
|||
|
//
|
|||
|
#define SHIFTJIS_HIBYTE_START0 0x81
|
|||
|
#define SHIFTJIS_HIBYTE_STOP0 0x9F
|
|||
|
#define SHIFTJIS_HIBYTE_START1 0xE0
|
|||
|
#define SHIFTJIS_HIBYTE_STOP1 0xEF
|
|||
|
//
|
|||
|
#define SHIFTJIS_LOBYTE_START0 0x40
|
|||
|
#define SHIFTJIS_LOBYTE_STOP0 0x7E
|
|||
|
#define SHIFTJIS_LOBYTE_START1 0x80
|
|||
|
#define SHIFTJIS_LOBYTE_STOP1 0xFC
|
|||
|
#define SHIFTJIS_CODES_PER_ROW (((SHIFTJIS_LOBYTE_STOP0-SHIFTJIS_LOBYTE_START0)+1)+((SHIFTJIS_LOBYTE_STOP1-SHIFTJIS_LOBYTE_START1)+1))
|
|||
|
|
|||
|
|
|||
|
extern qboolean Language_IsJapanese( void );
|
|||
|
|
|||
|
static bool Japanese_ValidShiftJISCode( byte _iHi, byte _iLo )
|
|||
|
{
|
|||
|
if ( (_iHi >= SHIFTJIS_HIBYTE_START0 && _iHi <= SHIFTJIS_HIBYTE_STOP0)
|
|||
|
|| (_iHi >= SHIFTJIS_HIBYTE_START1 && _iHi <= SHIFTJIS_HIBYTE_STOP1)
|
|||
|
)
|
|||
|
{
|
|||
|
if ( (_iLo >= SHIFTJIS_LOBYTE_START0 && _iLo <= SHIFTJIS_LOBYTE_STOP0) ||
|
|||
|
(_iLo >= SHIFTJIS_LOBYTE_START1 && _iLo <= SHIFTJIS_LOBYTE_STOP1)
|
|||
|
)
|
|||
|
{
|
|||
|
return true;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
static inline bool Japanese_ValidShiftJISCode( unsigned int uiCode )
|
|||
|
{
|
|||
|
return Japanese_ValidShiftJISCode( uiCode >> 8, uiCode & 0xFF );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
// only call this when Japanese_ValidShiftJISCode() has already returned true...
|
|||
|
//
|
|||
|
static bool Japanese_IsTrailingPunctuation( unsigned int uiCode )
|
|||
|
{
|
|||
|
// so far I'm just counting the first 18 chars, those seem to be all the basic punctuation...
|
|||
|
//
|
|||
|
if ( uiCode >= ((SHIFTJIS_HIBYTE_START0<<8)|SHIFTJIS_LOBYTE_START0) &&
|
|||
|
uiCode < ((SHIFTJIS_HIBYTE_START0<<8)|SHIFTJIS_LOBYTE_START0+18)
|
|||
|
)
|
|||
|
{
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
// takes a ShiftJIS double-byte code and collapse down to a 0..n glyph index...
|
|||
|
//
|
|||
|
// (invalid codes will return 0)
|
|||
|
//
|
|||
|
static int Japanese_CollapseShiftJISCode( unsigned int uiCode )
|
|||
|
{
|
|||
|
if (Japanese_ValidShiftJISCode( uiCode ))
|
|||
|
{
|
|||
|
uiCode -= ((SHIFTJIS_HIBYTE_START0<<8)|SHIFTJIS_LOBYTE_START0); // sneaky maths on both bytes, reduce to 0x0000 onwards
|
|||
|
|
|||
|
if ( (uiCode & 0xFF) >= (SHIFTJIS_LOBYTE_START1)-SHIFTJIS_LOBYTE_START0)
|
|||
|
{
|
|||
|
uiCode -= ((SHIFTJIS_LOBYTE_START1)-SHIFTJIS_LOBYTE_STOP0)-1;
|
|||
|
}
|
|||
|
|
|||
|
if ( ((uiCode>>8)&0xFF) >= (SHIFTJIS_HIBYTE_START1)-SHIFTJIS_HIBYTE_START0)
|
|||
|
{
|
|||
|
uiCode -= (((SHIFTJIS_HIBYTE_START1)-SHIFTJIS_HIBYTE_STOP0)-1) << 8;
|
|||
|
}
|
|||
|
|
|||
|
uiCode = ((uiCode >> 8) * SHIFTJIS_CODES_PER_ROW) + (uiCode & 0xFF);
|
|||
|
|
|||
|
return uiCode;
|
|||
|
}
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
static int Japanese_InitFields(int &iGlyphTPs, LPCSTR &psLang)
|
|||
|
{
|
|||
|
psLang = "jap";
|
|||
|
iGlyphTPs = GLYPH_MAX_JAPANESE_SHADERS;
|
|||
|
g_iNonScaledCharRange = 255;
|
|||
|
return 64; // m_iAsianGlyphsAcross
|
|||
|
}
|
|||
|
|
|||
|
// ======================== some Chinese stuff ==============================
|
|||
|
|
|||
|
#define GB_HIBYTE_START 0xA1 // range is...
|
|||
|
#define GB_HIBYTE_STOP 0xF7 // ... inclusive
|
|||
|
#define GB_LOBYTE_LOBOUND 0xA0 // range is...
|
|||
|
#define GB_LOBYTE_HIBOUND 0xFF // ...bounding (ie only valid in between these points, but NULLs in charsets for these codes)
|
|||
|
#define GB_CODES_PER_ROW 95 // 1 more than the number of glyphs
|
|||
|
|
|||
|
extern qboolean Language_IsChinese( void );
|
|||
|
|
|||
|
static inline bool Chinese_ValidGBCode( byte _iHi, byte _iLo )
|
|||
|
{
|
|||
|
return (_iHi >=GB_HIBYTE_START &&
|
|||
|
_iHi <=GB_HIBYTE_STOP &&
|
|||
|
_iLo > GB_LOBYTE_LOBOUND &&
|
|||
|
_iLo < GB_LOBYTE_HIBOUND
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
static inline bool Chinese_ValidGBCode( unsigned int uiCode)
|
|||
|
{
|
|||
|
return Chinese_ValidGBCode( uiCode >> 8, uiCode & 0xFF );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
// only call this when Chinese_ValidGBCode() has already returned true...
|
|||
|
//
|
|||
|
static bool Chinese_IsTrailingPunctuation( unsigned int uiCode )
|
|||
|
{
|
|||
|
// so far I'm just counting the first 13 chars, those seem to be all the basic punctuation...
|
|||
|
//
|
|||
|
if ( uiCode > ((GB_HIBYTE_START<<8)|GB_LOBYTE_LOBOUND) &&
|
|||
|
uiCode < ((GB_HIBYTE_START<<8)|GB_LOBYTE_LOBOUND+14)
|
|||
|
)
|
|||
|
{
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
// takes a GB double-byte 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 GB codes will return 0)
|
|||
|
//
|
|||
|
static int Chinese_CollapseGBCode( unsigned int uiCode )
|
|||
|
{
|
|||
|
if (Chinese_ValidGBCode( uiCode ))
|
|||
|
{
|
|||
|
uiCode -= (GB_HIBYTE_START * 256) + GB_LOBYTE_LOBOUND; // sneaky maths on both bytes, reduce to 0x0000 onwards
|
|||
|
uiCode = ((uiCode >> 8) * GB_CODES_PER_ROW) + (uiCode & 0xFF);
|
|||
|
return uiCode;
|
|||
|
}
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
static int Chinese_InitFields(int &iGlyphTPs, LPCSTR &psLang)
|
|||
|
{
|
|||
|
psLang = "chi";
|
|||
|
iGlyphTPs = GLYPH_MAX_CHINESE_SHADERS;
|
|||
|
g_iNonScaledCharRange = 255;
|
|||
|
return 64; // m_iAsianGlyphsAcross
|
|||
|
}
|
|||
|
|
|||
|
// ======================== some Thai stuff ==============================
|
|||
|
|
|||
|
//TIS 620-2533
|
|||
|
|
|||
|
#define TIS_GLYPHS_START 160
|
|||
|
#define TIS_SARA_AM 0xD3 // special case letter, both a new letter and a trailing accent for the prev one
|
|||
|
ThaiCodes_t g_ThaiCodes; // the one and only instance of this object
|
|||
|
|
|||
|
extern qboolean Language_IsThai( void );
|
|||
|
|
|||
|
/*
|
|||
|
static int Thai_IsAccentChar( unsigned int uiCode )
|
|||
|
{
|
|||
|
switch (uiCode)
|
|||
|
{
|
|||
|
case 209:
|
|||
|
case 212: case 213: case 214: case 215: case 216: case 217: case 218:
|
|||
|
case 231: case 232: case 233: case 234: case 235: case 236: case 237: case 238:
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
return false;
|
|||
|
}
|
|||
|
*/
|
|||
|
|
|||
|
// returns a valid Thai code (or 0), based on taking 1,2 or 3 bytes from the supplied byte stream
|
|||
|
// Fills in <iThaiBytes> with 1,2 or 3
|
|||
|
static int Thai_ValidTISCode( const byte *psString, int &iThaiBytes )
|
|||
|
{
|
|||
|
// try a 1-byte code first...
|
|||
|
//
|
|||
|
if (psString[0] >= 160) // so western letters drop through and use normal font
|
|||
|
{
|
|||
|
// this code is heavily little-endian, so someone else will need to port for Mac etc... (not my problem ;-)
|
|||
|
//
|
|||
|
union CodeToTry_t
|
|||
|
{
|
|||
|
char sChars[4];
|
|||
|
unsigned int uiCode;
|
|||
|
};
|
|||
|
|
|||
|
CodeToTry_t CodeToTry;
|
|||
|
CodeToTry.uiCode = 0; // important that we clear all 4 bytes in sChars here
|
|||
|
|
|||
|
// thai codes can be up to 3 bytes long, so see how high we can get...
|
|||
|
//
|
|||
|
for (int i=0; i<3; i++)
|
|||
|
{
|
|||
|
CodeToTry.sChars[i] = psString[i];
|
|||
|
|
|||
|
int iIndex = g_ThaiCodes.GetValidIndex( CodeToTry.uiCode );
|
|||
|
if (iIndex == -1)
|
|||
|
{
|
|||
|
// failed, so return previous-longest code...
|
|||
|
//
|
|||
|
CodeToTry.sChars[i] = 0;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
iThaiBytes = i;
|
|||
|
assert(i); // if 'i' was 0, then this may be an error, trying to get a thai accent as standalone char?
|
|||
|
return CodeToTry.uiCode;
|
|||
|
}
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
// special case, thai can only break on certain letters, and since the rules are complicated then
|
|||
|
// we tell the translators to put an underscore ('_') between each word even though in Thai they're
|
|||
|
// all jammed together at final output onscreen...
|
|||
|
//
|
|||
|
static inline bool Thai_IsTrailingPunctuation( unsigned int uiCode )
|
|||
|
{
|
|||
|
return uiCode == '_';
|
|||
|
}
|
|||
|
|
|||
|
// takes a TIS 1,2 or 3 byte code and collapse down to a 0..n glyph index...
|
|||
|
//
|
|||
|
// (invalid codes will return 0)
|
|||
|
//
|
|||
|
static int Thai_CollapseTISCode( unsigned int uiCode )
|
|||
|
{
|
|||
|
if (uiCode >= TIS_GLYPHS_START) // so western letters drop through as invalid
|
|||
|
{
|
|||
|
int iCollapsedIndex = g_ThaiCodes.GetValidIndex( uiCode );
|
|||
|
if (iCollapsedIndex != -1)
|
|||
|
{
|
|||
|
return iCollapsedIndex;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
static int Thai_InitFields(int &iGlyphTPs, LPCSTR &psLang)
|
|||
|
{
|
|||
|
psLang = "tha";
|
|||
|
iGlyphTPs = GLYPH_MAX_THAI_SHADERS;
|
|||
|
g_iNonScaledCharRange = INT_MAX; // in other words, don't scale any thai chars down
|
|||
|
return 32; // m_iAsianGlyphsAcross
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
// ============================================================================
|
|||
|
|
|||
|
// 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)...
|
|||
|
//
|
|||
|
// looks messy, but the actual execution route is quite short, so it's fast...
|
|||
|
//
|
|||
|
// Note that I have to have this 3-param form instead of advancing a passed-in "const char **psText" because of VM-crap where you can only change ptr-contents, not ptrs themselves. Bleurgh. Ditto the qtrue:qfalse crap instead of just returning stuff straight through.
|
|||
|
//
|
|||
|
unsigned int AnyLanguage_ReadCharFromString( const char *psText, int *piAdvanceCount, qboolean *pbIsTrailingPunctuation /* = NULL */)
|
|||
|
{
|
|||
|
const byte *psString = (const byte *) psText; // avoid sign-promote bug
|
|||
|
unsigned int uiLetter;
|
|||
|
|
|||
|
switch ( GetLanguageEnum() )
|
|||
|
{
|
|||
|
case eKorean:
|
|||
|
{
|
|||
|
if ( Korean_ValidKSC5601Hangul( psString[0], psString[1] ))
|
|||
|
{
|
|||
|
uiLetter = (psString[0] * 256) + psString[1];
|
|||
|
*piAdvanceCount = 2;
|
|||
|
|
|||
|
// not going to bother testing for korean punctuation here, since korean already
|
|||
|
// uses spaces, and I don't have the punctuation glyphs defined, only the basic 2350 hanguls
|
|||
|
//
|
|||
|
if ( pbIsTrailingPunctuation)
|
|||
|
{
|
|||
|
*pbIsTrailingPunctuation = qfalse;
|
|||
|
}
|
|||
|
|
|||
|
return uiLetter;
|
|||
|
}
|
|||
|
}
|
|||
|
break;
|
|||
|
|
|||
|
case eTaiwanese:
|
|||
|
{
|
|||
|
if ( Taiwanese_ValidBig5Code( (psString[0] * 256) + psString[1] ))
|
|||
|
{
|
|||
|
uiLetter = (psString[0] * 256) + psString[1];
|
|||
|
*piAdvanceCount = 2;
|
|||
|
|
|||
|
// need to ask if this is a trailing (ie like a comma or full-stop) punctuation?...
|
|||
|
//
|
|||
|
if ( pbIsTrailingPunctuation)
|
|||
|
{
|
|||
|
*pbIsTrailingPunctuation = Taiwanese_IsTrailingPunctuation( uiLetter ) ? qtrue : qfalse;
|
|||
|
}
|
|||
|
|
|||
|
return uiLetter;
|
|||
|
}
|
|||
|
}
|
|||
|
break;
|
|||
|
|
|||
|
case eJapanese:
|
|||
|
{
|
|||
|
if ( Japanese_ValidShiftJISCode( psString[0], psString[1] ))
|
|||
|
{
|
|||
|
uiLetter = (psString[0] * 256) + psString[1];
|
|||
|
*piAdvanceCount = 2;
|
|||
|
|
|||
|
// need to ask if this is a trailing (ie like a comma or full-stop) punctuation?...
|
|||
|
//
|
|||
|
if ( pbIsTrailingPunctuation)
|
|||
|
{
|
|||
|
*pbIsTrailingPunctuation = Japanese_IsTrailingPunctuation( uiLetter ) ? qtrue : qfalse;
|
|||
|
}
|
|||
|
|
|||
|
return uiLetter;
|
|||
|
}
|
|||
|
}
|
|||
|
break;
|
|||
|
|
|||
|
case eChinese:
|
|||
|
{
|
|||
|
if ( Chinese_ValidGBCode( (psString[0] * 256) + psString[1] ))
|
|||
|
{
|
|||
|
uiLetter = (psString[0] * 256) + psString[1];
|
|||
|
*piAdvanceCount = 2;
|
|||
|
|
|||
|
// need to ask if this is a trailing (ie like a comma or full-stop) punctuation?...
|
|||
|
//
|
|||
|
if ( pbIsTrailingPunctuation)
|
|||
|
{
|
|||
|
*pbIsTrailingPunctuation = Chinese_IsTrailingPunctuation( uiLetter ) ? qtrue : qfalse;
|
|||
|
}
|
|||
|
|
|||
|
return uiLetter;
|
|||
|
}
|
|||
|
}
|
|||
|
break;
|
|||
|
|
|||
|
case eThai:
|
|||
|
{
|
|||
|
int iThaiBytes;
|
|||
|
uiLetter = Thai_ValidTISCode( psString, iThaiBytes );
|
|||
|
if ( uiLetter )
|
|||
|
{
|
|||
|
*piAdvanceCount = iThaiBytes;
|
|||
|
|
|||
|
if ( pbIsTrailingPunctuation )
|
|||
|
{
|
|||
|
*pbIsTrailingPunctuation = Thai_IsTrailingPunctuation( uiLetter ) ? qtrue : qfalse;
|
|||
|
}
|
|||
|
|
|||
|
return uiLetter;
|
|||
|
}
|
|||
|
}
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
// ... must not have been an MBCS code...
|
|||
|
//
|
|||
|
uiLetter = psString[0];
|
|||
|
*piAdvanceCount = 1;
|
|||
|
|
|||
|
if (pbIsTrailingPunctuation)
|
|||
|
{
|
|||
|
*pbIsTrailingPunctuation = (uiLetter == '!' ||
|
|||
|
uiLetter == '?' ||
|
|||
|
uiLetter == ',' ||
|
|||
|
uiLetter == '.' ||
|
|||
|
uiLetter == ';' ||
|
|||
|
uiLetter == ':'
|
|||
|
) ? qtrue : qfalse;
|
|||
|
}
|
|||
|
|
|||
|
return uiLetter;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
// needed for subtitle printing since original code no longer worked once camera bar height was changed to 480/10
|
|||
|
// rather than refdef height / 10. I now need to bodge the coords to come out right.
|
|||
|
//
|
|||
|
qboolean Language_IsAsian(void)
|
|||
|
{
|
|||
|
switch ( GetLanguageEnum() )
|
|||
|
{
|
|||
|
case eKorean:
|
|||
|
case eTaiwanese:
|
|||
|
case eJapanese:
|
|||
|
case eChinese:
|
|||
|
case eThai: // this is asian, but the query is normally used for scaling
|
|||
|
return qtrue;
|
|||
|
}
|
|||
|
|
|||
|
return qfalse;
|
|||
|
}
|
|||
|
|
|||
|
qboolean Language_UsesSpaces(void)
|
|||
|
{
|
|||
|
// ( korean uses spaces )
|
|||
|
switch ( GetLanguageEnum() )
|
|||
|
{
|
|||
|
case eTaiwanese:
|
|||
|
case eJapanese:
|
|||
|
case eChinese:
|
|||
|
case eThai:
|
|||
|
return qfalse;
|
|||
|
}
|
|||
|
|
|||
|
return qtrue;
|
|||
|
}
|
|||
|
|
|||
|
// ======================================================================
|
|||
|
// name is (eg) "ergo" or "lcd", no extension.
|
|||
|
//
|
|||
|
// If path present, it's a special language hack for SBCS override languages, eg: "lcd/russian", which means
|
|||
|
// just treat the file as "russian", but with the "lcd" part ensuring we don't find a different registered russian font
|
|||
|
//
|
|||
|
CFontInfo::CFontInfo(const char *_fontName)
|
|||
|
{
|
|||
|
int len, i;
|
|||
|
void *buff;
|
|||
|
dfontdat_t *fontdat;
|
|||
|
|
|||
|
// remove any special hack name insertions...
|
|||
|
//
|
|||
|
char fontName[MAX_QPATH];
|
|||
|
sprintf(fontName,"fonts/%s.fontdat",COM_SkipPath(const_cast<char*>(_fontName))); // COM_SkipPath should take a const char *, but it's just possible people use it as a char * I guess, so I have to hack around like this <groan>
|
|||
|
|
|||
|
// clear some general things...
|
|||
|
//
|
|||
|
m_pThaiData = NULL;
|
|||
|
m_iAltSBCSFont = -1;
|
|||
|
m_iThisFont = -1;
|
|||
|
m_iOriginalFontWhenSBCSOverriden = -1;
|
|||
|
m_fAltSBCSFontScaleFactor = -1;
|
|||
|
m_bIsFakeAlienLanguage = !strcmp(_fontName,"aurabesh"); // dont try and make SBCS or asian overrides for this
|
|||
|
|
|||
|
len = FS_ReadFile(fontName, NULL);
|
|||
|
if (len == sizeof(dfontdat_t))
|
|||
|
{
|
|||
|
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; // ignore this crap, it's some junk in the fontdat file that no-one uses
|
|||
|
mbRoundCalcs = !!strstr(fontName,"ergo");
|
|||
|
|
|||
|
// cope with bad fontdat headers...
|
|||
|
//
|
|||
|
if (mHeight == 0)
|
|||
|
{
|
|||
|
mHeight = mPointSize;
|
|||
|
mAscender = mPointSize - Round( ((float)mPointSize/10.0f)+2 ); // have to completely guess at the baseline... sigh.
|
|||
|
mDescender = mHeight - mAscender;
|
|||
|
}
|
|||
|
|
|||
|
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...
|
|||
|
g_vFontArray.resize(g_iCurrentFontIndex + 1);
|
|||
|
g_vFontArray[g_iCurrentFontIndex++] = this;
|
|||
|
|
|||
|
|
|||
|
extern cvar_t *com_buildScript;
|
|||
|
if (com_buildScript->integer == 2)
|
|||
|
{
|
|||
|
Com_Printf( "com_buildScript(2): Registering foreign fonts...\n" );
|
|||
|
static qboolean bDone = qfalse; // Do this once only (for speed)...
|
|||
|
if (!bDone)
|
|||
|
{
|
|||
|
bDone = qtrue;
|
|||
|
|
|||
|
char sTemp[MAX_QPATH];
|
|||
|
int iGlyphTPs = 0;
|
|||
|
const char *psLang = NULL;
|
|||
|
|
|||
|
// SBCS override languages...
|
|||
|
//
|
|||
|
fileHandle_t f;
|
|||
|
for (int i=0; g_SBCSOverrideLanguages[i].m_psName ;i++)
|
|||
|
{
|
|||
|
char sTemp[MAX_QPATH];
|
|||
|
|
|||
|
sprintf(sTemp,"fonts/%s.tga", g_SBCSOverrideLanguages[i].m_psName );
|
|||
|
FS_FOpenFileRead( sTemp, &f, qfalse );
|
|||
|
if (f) FS_FCloseFile( f );
|
|||
|
|
|||
|
sprintf(sTemp,"fonts/%s.fontdat", g_SBCSOverrideLanguages[i].m_psName );
|
|||
|
FS_FOpenFileRead( sTemp, &f, qfalse );
|
|||
|
if (f) FS_FCloseFile( f );
|
|||
|
}
|
|||
|
|
|||
|
// asian MBCS override languages...
|
|||
|
//
|
|||
|
for (int iLang=0; iLang<5; iLang++)
|
|||
|
{
|
|||
|
switch (iLang)
|
|||
|
{
|
|||
|
case 0: m_iAsianGlyphsAcross = Korean_InitFields (iGlyphTPs, psLang); break;
|
|||
|
case 1: m_iAsianGlyphsAcross = Taiwanese_InitFields (iGlyphTPs, psLang); break;
|
|||
|
case 2: m_iAsianGlyphsAcross = Japanese_InitFields (iGlyphTPs, psLang); break;
|
|||
|
case 3: m_iAsianGlyphsAcross = Chinese_InitFields (iGlyphTPs, psLang); break;
|
|||
|
case 4: m_iAsianGlyphsAcross = Thai_InitFields (iGlyphTPs, psLang);
|
|||
|
{
|
|||
|
// additional files needed for Thai language...
|
|||
|
//
|
|||
|
FS_FOpenFileRead( sFILENAME_THAI_WIDTHS , &f, qfalse );
|
|||
|
if (f) {
|
|||
|
FS_FCloseFile( f );
|
|||
|
}
|
|||
|
|
|||
|
FS_FOpenFileRead( sFILENAME_THAI_CODES, &f, qfalse );
|
|||
|
if (f) {
|
|||
|
FS_FCloseFile( f );
|
|||
|
}
|
|||
|
}
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
for (int i=0; i<iGlyphTPs; i++)
|
|||
|
{
|
|||
|
Com_sprintf(sTemp,sizeof(sTemp), "fonts/%s_%d_1024_%d.tga", psLang, 1024/m_iAsianGlyphsAcross, i);
|
|||
|
|
|||
|
// RE_RegisterShaderNoMip( sTemp ); // don't actually need to load it, so...
|
|||
|
FS_FOpenFileRead( sTemp, &f, qfalse );
|
|||
|
if (f) {
|
|||
|
FS_FCloseFile( f );
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
void CFontInfo::UpdateAsianIfNeeded( bool bForceReEval /* = false */ )
|
|||
|
{
|
|||
|
// if asian language, then provide an alternative glyph set and fill in relevant fields...
|
|||
|
//
|
|||
|
if (mHeight && !m_bIsFakeAlienLanguage) // western charset exists in first place, and isn't alien rubbish?
|
|||
|
{
|
|||
|
Language_e eLanguage = GetLanguageEnum();
|
|||
|
|
|||
|
if (eLanguage == eKorean || eLanguage == eTaiwanese || eLanguage == eJapanese || eLanguage == eChinese || eLanguage == eThai)
|
|||
|
{
|
|||
|
int iCappedHeight = mHeight < 16 ? 16: mHeight; // arbitrary limit on small char sizes because Asian chars don't squash well
|
|||
|
|
|||
|
if (m_iLanguageModificationCount != se_language->modificationCount || !AsianGlyphsAvailable() || bForceReEval)
|
|||
|
{
|
|||
|
m_iLanguageModificationCount = se_language->modificationCount;
|
|||
|
|
|||
|
int iGlyphTPs = 0;
|
|||
|
const char *psLang = NULL;
|
|||
|
|
|||
|
switch ( eLanguage )
|
|||
|
{
|
|||
|
case eKorean: m_iAsianGlyphsAcross = Korean_InitFields(iGlyphTPs, psLang); break;
|
|||
|
case eTaiwanese: m_iAsianGlyphsAcross = Taiwanese_InitFields(iGlyphTPs, psLang); break;
|
|||
|
case eJapanese: m_iAsianGlyphsAcross = Japanese_InitFields(iGlyphTPs, psLang); break;
|
|||
|
case eChinese: m_iAsianGlyphsAcross = Chinese_InitFields(iGlyphTPs, psLang); break;
|
|||
|
case eThai:
|
|||
|
{
|
|||
|
m_iAsianGlyphsAcross = Thai_InitFields(iGlyphTPs, psLang);
|
|||
|
|
|||
|
if (!m_pThaiData)
|
|||
|
{
|
|||
|
LPCSTR psFailureReason = g_ThaiCodes.Init();
|
|||
|
if (!psFailureReason[0])
|
|||
|
{
|
|||
|
m_pThaiData = &g_ThaiCodes;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// failed to load a needed file, reset to English...
|
|||
|
//
|
|||
|
Cvar_Set("se_language", "english");
|
|||
|
Com_Error( ERR_DROP, psFailureReason );
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
// 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; // ""
|
|||
|
switch (eLanguage)
|
|||
|
{
|
|||
|
default: m_AsianGlyph.horizAdvance = iCappedHeight; break;
|
|||
|
case eKorean: m_AsianGlyph.horizAdvance = iCappedHeight - 1;break; // korean has a small amount of space at the edge of the glyph
|
|||
|
|
|||
|
case eTaiwanese:
|
|||
|
case eJapanese:
|
|||
|
case eChinese: m_AsianGlyph.horizAdvance = iCappedHeight + 3; // need to force some spacing for these
|
|||
|
// case eThai: // this is done dynamically elsewhere, since Thai glyphs are variable width
|
|||
|
}
|
|||
|
m_AsianGlyph.horizOffset = 0; // ""
|
|||
|
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();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
static CFontInfo *GetFont_Actual(int index)
|
|||
|
{
|
|||
|
index &= SET_MASK;
|
|||
|
if((index >= 1) && (index < g_iCurrentFontIndex))
|
|||
|
{
|
|||
|
CFontInfo *pFont = g_vFontArray[index];
|
|||
|
|
|||
|
if (pFont)
|
|||
|
{
|
|||
|
pFont->UpdateAsianIfNeeded();
|
|||
|
}
|
|||
|
|
|||
|
return pFont;
|
|||
|
}
|
|||
|
return(NULL);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
// needed to add *piShader param because of multiple TPs,
|
|||
|
// if not passed in, then I also skip S,T calculations for re-usable static asian glyphinfo struct...
|
|||
|
//
|
|||
|
const glyphInfo_t *CFontInfo::GetLetter(const unsigned int uiLetter, int *piShader /* = NULL */)
|
|||
|
{
|
|||
|
if ( AsianGlyphsAvailable() )
|
|||
|
{
|
|||
|
int iCollapsedAsianCode = GetCollapsedAsianCode( uiLetter );
|
|||
|
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) - but not for Thai
|
|||
|
//
|
|||
|
int iTexturePageIndex = iCollapsedAsianCode / (m_iAsianGlyphsAcross * m_iAsianGlyphsAcross);
|
|||
|
|
|||
|
if (iTexturePageIndex > m_iAsianPagesLoaded)
|
|||
|
{
|
|||
|
assert(0); // should never happen
|
|||
|
iTexturePageIndex = 0;
|
|||
|
}
|
|||
|
|
|||
|
int iOriginalCollapsedAsianCode = iCollapsedAsianCode; // need to back this up (if Thai) for later
|
|||
|
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;
|
|||
|
|
|||
|
switch ( GetLanguageEnum() )
|
|||
|
{
|
|||
|
case eKorean:
|
|||
|
default:
|
|||
|
{
|
|||
|
m_AsianGlyph.s = (float)( iColumn ) / (float)m_iAsianGlyphsAcross;
|
|||
|
m_AsianGlyph.t = (float)( iRow ) / (float) iAsianGlyphsDown;
|
|||
|
m_AsianGlyph.s2 = (float)( iColumn + 1) / (float)m_iAsianGlyphsAcross;
|
|||
|
m_AsianGlyph.t2 = (float)( iRow + 1 ) / (float) iAsianGlyphsDown;
|
|||
|
}
|
|||
|
break;
|
|||
|
|
|||
|
case eTaiwanese:
|
|||
|
{
|
|||
|
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)(((1024 / m_iAsianGlyphsAcross) * ( iColumn+1 )) ) / 1024.0f;
|
|||
|
m_AsianGlyph.t2 = (float)(((1024 / iAsianGlyphsDown ) * ( iRow+1 )) ) / 1024.0f;
|
|||
|
}
|
|||
|
break;
|
|||
|
|
|||
|
case eJapanese:
|
|||
|
case eChinese:
|
|||
|
{
|
|||
|
m_AsianGlyph.s = (float)(((1024 / m_iAsianGlyphsAcross) * ( iColumn )) ) / 1024.0f;
|
|||
|
m_AsianGlyph.t = (float)(((1024 / iAsianGlyphsDown ) * ( iRow )) ) / 1024.0f;
|
|||
|
m_AsianGlyph.s2 = (float)(((1024 / m_iAsianGlyphsAcross) * ( iColumn+1 ))-1) / 1024.0f;
|
|||
|
m_AsianGlyph.t2 = (float)(((1024 / iAsianGlyphsDown ) * ( iRow+1 ))-1) / 1024.0f;
|
|||
|
}
|
|||
|
break;
|
|||
|
|
|||
|
case eThai:
|
|||
|
{
|
|||
|
int iGlyphXpos = (1024 / m_iAsianGlyphsAcross) * ( iColumn );
|
|||
|
int iGlyphWidth = g_ThaiCodes.GetWidth( iOriginalCollapsedAsianCode );
|
|||
|
|
|||
|
// very thai-specific language-code...
|
|||
|
//
|
|||
|
if (uiLetter == TIS_SARA_AM)
|
|||
|
{
|
|||
|
iGlyphXpos += 9; // these are pixel coords on the source TP, so don't affect scaled output
|
|||
|
iGlyphWidth= 20; //
|
|||
|
}
|
|||
|
m_AsianGlyph.s = (float)(iGlyphXpos) / 1024.0f;
|
|||
|
m_AsianGlyph.t = (float)(((1024 / iAsianGlyphsDown ) * ( iRow )) ) / 1024.0f;
|
|||
|
// technically this .s2 line should be modified to blit only the correct width, but since
|
|||
|
// all Thai glyphs are up against the left edge of their cells and have blank to the cell
|
|||
|
// boundary then it's better to keep these calculations simpler...
|
|||
|
|
|||
|
m_AsianGlyph.s2 = (float)(iGlyphXpos+iGlyphWidth) / 1024.0f;
|
|||
|
m_AsianGlyph.t2 = (float)(((1024 / iAsianGlyphsDown ) * ( iRow+1 ))-1) / 1024.0f;
|
|||
|
|
|||
|
// special addition for Thai, need to bodge up the width and advance fields...
|
|||
|
//
|
|||
|
m_AsianGlyph.width = iGlyphWidth;
|
|||
|
m_AsianGlyph.horizAdvance = iGlyphWidth + 1;
|
|||
|
}
|
|||
|
break;
|
|||
|
}
|
|||
|
*piShader = m_hAsianShaders[ iTexturePageIndex ];
|
|||
|
}
|
|||
|
return &m_AsianGlyph;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (piShader)
|
|||
|
{
|
|||
|
*piShader = GetShader();
|
|||
|
}
|
|||
|
|
|||
|
const glyphInfo_t *pGlyph = &mGlyphs[ uiLetter & 0xff ];
|
|||
|
//
|
|||
|
// SBCS language substitution?...
|
|||
|
//
|
|||
|
if ( m_fAltSBCSFontScaleFactor != -1 )
|
|||
|
{
|
|||
|
// sod it, use the asian glyph, that's fine...
|
|||
|
//
|
|||
|
memcpy(&m_AsianGlyph,pGlyph,sizeof(m_AsianGlyph)); // *before* changin pGlyph!
|
|||
|
|
|||
|
// CFontInfo *pOriginalFont = GetFont_Actual( this->m_iOriginalFontWhenSBCSOverriden );
|
|||
|
// pGlyph = &pOriginalFont->mGlyphs[ uiLetter & 0xff ];
|
|||
|
|
|||
|
#define ASSIGN_WITH_ROUNDING(_dst,_src) _dst = mbRoundCalcs ? Round( m_fAltSBCSFontScaleFactor * _src ) : m_fAltSBCSFontScaleFactor * (float)_src;
|
|||
|
|
|||
|
ASSIGN_WITH_ROUNDING( m_AsianGlyph.baseline, pGlyph->baseline );
|
|||
|
ASSIGN_WITH_ROUNDING( m_AsianGlyph.height, pGlyph->height );
|
|||
|
ASSIGN_WITH_ROUNDING( m_AsianGlyph.horizAdvance,pGlyph->horizAdvance );
|
|||
|
// m_AsianGlyph.horizOffset = /*Round*/( m_fAltSBCSFontScaleFactor * pGlyph->horizOffset );
|
|||
|
ASSIGN_WITH_ROUNDING( m_AsianGlyph.width, pGlyph->width );
|
|||
|
|
|||
|
pGlyph = &m_AsianGlyph;
|
|||
|
}
|
|||
|
|
|||
|
return pGlyph;
|
|||
|
}
|
|||
|
|
|||
|
const int CFontInfo::GetCollapsedAsianCode(ulong uiLetter) const
|
|||
|
{
|
|||
|
int iCollapsedAsianCode = 0;
|
|||
|
|
|||
|
if (AsianGlyphsAvailable())
|
|||
|
{
|
|||
|
switch ( GetLanguageEnum() )
|
|||
|
{
|
|||
|
case eKorean: iCollapsedAsianCode = Korean_CollapseKSC5601HangulCode( uiLetter ); break;
|
|||
|
case eTaiwanese: iCollapsedAsianCode = Taiwanese_CollapseBig5Code( uiLetter ); break;
|
|||
|
case eJapanese: iCollapsedAsianCode = Japanese_CollapseShiftJISCode( uiLetter ); break;
|
|||
|
case eChinese: iCollapsedAsianCode = Chinese_CollapseGBCode( uiLetter ); break;
|
|||
|
case eThai: iCollapsedAsianCode = Thai_CollapseTISCode( uiLetter ); break;
|
|||
|
default: assert(0); /* unhandled asian language */ break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return iCollapsedAsianCode;
|
|||
|
}
|
|||
|
|
|||
|
const int CFontInfo::GetLetterWidth(unsigned int uiLetter)
|
|||
|
{
|
|||
|
const glyphInfo_t *pGlyph = GetLetter( uiLetter );
|
|||
|
return pGlyph->width ? pGlyph->width : mGlyphs['.'].width;
|
|||
|
}
|
|||
|
|
|||
|
const int CFontInfo::GetLetterHorizAdvance(unsigned int uiLetter)
|
|||
|
{
|
|||
|
const glyphInfo_t *pGlyph = GetLetter( uiLetter );
|
|||
|
return pGlyph->horizAdvance ? pGlyph->horizAdvance : mGlyphs['.'].horizAdvance;
|
|||
|
}
|
|||
|
|
|||
|
// ensure any GetFont calls that need SBCS overriding (such as when playing in Russian) have the appropriate stuff done...
|
|||
|
//
|
|||
|
static CFontInfo *GetFont_SBCSOverride(CFontInfo *pFont, Language_e eLanguageSBCS, LPCSTR psLanguageNameSBCS )
|
|||
|
{
|
|||
|
if ( !pFont->m_bIsFakeAlienLanguage )
|
|||
|
{
|
|||
|
if ( GetLanguageEnum() == eLanguageSBCS )
|
|||
|
{
|
|||
|
if ( pFont->m_iAltSBCSFont == -1 ) // no reg attempted yet?
|
|||
|
{
|
|||
|
// need to register this alternative SBCS font...
|
|||
|
//
|
|||
|
int iAltFontIndex = RE_RegisterFont( va("%s/%s",COM_SkipPath(pFont->m_sFontName),psLanguageNameSBCS) ); // ensure unique name (eg: "lcd/russian")
|
|||
|
CFontInfo *pAltFont = GetFont_Actual( iAltFontIndex );
|
|||
|
if ( pAltFont )
|
|||
|
{
|
|||
|
// work out the scaling factor for this font's glyphs...( round it to 1 decimal place to cut down on silly scale factors like 0.53125 )
|
|||
|
//
|
|||
|
pAltFont->m_fAltSBCSFontScaleFactor = RoundTenth((float)pFont->GetPointSize() / (float)pAltFont->GetPointSize());
|
|||
|
//
|
|||
|
// then override with the main properties of the original font...
|
|||
|
//
|
|||
|
pAltFont->mPointSize = pFont->GetPointSize();//(float) pAltFont->GetPointSize() * pAltFont->m_fAltSBCSFontScaleFactor;
|
|||
|
pAltFont->mHeight = pFont->GetHeight();//(float) pAltFont->GetHeight() * pAltFont->m_fAltSBCSFontScaleFactor;
|
|||
|
pAltFont->mAscender = pFont->GetAscender();//(float) pAltFont->GetAscender() * pAltFont->m_fAltSBCSFontScaleFactor;
|
|||
|
pAltFont->mDescender = pFont->GetDescender();//(float) pAltFont->GetDescender() * pAltFont->m_fAltSBCSFontScaleFactor;
|
|||
|
|
|||
|
// pAltFont->mPointSize = (float) pAltFont->GetPointSize() * pAltFont->m_fAltSBCSFontScaleFactor;
|
|||
|
// pAltFont->mHeight = (float) pAltFont->GetHeight() * pAltFont->m_fAltSBCSFontScaleFactor;
|
|||
|
// pAltFont->mAscender = (float) pAltFont->GetAscender() * pAltFont->m_fAltSBCSFontScaleFactor;
|
|||
|
// pAltFont->mDescender = (float) pAltFont->GetDescender() * pAltFont->m_fAltSBCSFontScaleFactor;
|
|||
|
|
|||
|
pAltFont->mbRoundCalcs = true;
|
|||
|
pAltFont->m_iOriginalFontWhenSBCSOverriden = pFont->m_iThisFont;
|
|||
|
}
|
|||
|
pFont->m_iAltSBCSFont = iAltFontIndex;
|
|||
|
}
|
|||
|
|
|||
|
if ( pFont->m_iAltSBCSFont > 0)
|
|||
|
{
|
|||
|
return GetFont_Actual( pFont->m_iAltSBCSFont );
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
CFontInfo *GetFont(int index)
|
|||
|
{
|
|||
|
CFontInfo *pFont = GetFont_Actual( index );
|
|||
|
|
|||
|
if (pFont)
|
|||
|
{
|
|||
|
// any SBCS overrides? (this has to be pretty quick, and is (sort of))...
|
|||
|
//
|
|||
|
for (int i=0; g_SBCSOverrideLanguages[i].m_psName; i++)
|
|||
|
{
|
|||
|
CFontInfo *pAltFont = GetFont_SBCSOverride( pFont, g_SBCSOverrideLanguages[i].m_eLanguage, g_SBCSOverrideLanguages[i].m_psName );
|
|||
|
if (pAltFont)
|
|||
|
{
|
|||
|
return pAltFont;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return pFont;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
int RE_Font_StrLenPixels(const char *psText, const int iFontHandle, const float fScale)
|
|||
|
{
|
|||
|
int iMaxWidth = 0;
|
|||
|
int iThisWidth= 0;
|
|||
|
CFontInfo *curfont;
|
|||
|
|
|||
|
curfont = GetFont(iFontHandle);
|
|||
|
if(!curfont)
|
|||
|
{
|
|||
|
return(0);
|
|||
|
}
|
|||
|
|
|||
|
float fScaleA = fScale;
|
|||
|
if (Language_IsAsian() && fScale > 0.7f )
|
|||
|
{
|
|||
|
fScaleA = fScale * 0.75f;
|
|||
|
}
|
|||
|
|
|||
|
while(*psText)
|
|||
|
{
|
|||
|
int iAdvanceCount;
|
|||
|
unsigned int uiLetter = AnyLanguage_ReadCharFromString( psText, &iAdvanceCount, NULL );
|
|||
|
psText += iAdvanceCount;
|
|||
|
|
|||
|
if (uiLetter == '^' )
|
|||
|
{
|
|||
|
if (*psText >= '0' &&
|
|||
|
*psText <= '9')
|
|||
|
{
|
|||
|
uiLetter = AnyLanguage_ReadCharFromString( psText, &iAdvanceCount, NULL );
|
|||
|
psText += iAdvanceCount;
|
|||
|
continue;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (uiLetter == 0x0A)
|
|||
|
{
|
|||
|
iThisWidth = 0;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
int iPixelAdvance = curfont->GetLetterHorizAdvance( uiLetter );
|
|||
|
|
|||
|
float fValue = iPixelAdvance * ((uiLetter > g_iNonScaledCharRange) ? fScaleA : fScale);
|
|||
|
iThisWidth += curfont->mbRoundCalcs ? Round( fValue ) : fValue;
|
|||
|
if (iThisWidth > iMaxWidth)
|
|||
|
{
|
|||
|
iMaxWidth = iThisWidth;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return iMaxWidth;
|
|||
|
}
|
|||
|
|
|||
|
// 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...
|
|||
|
//
|
|||
|
int iAdvanceCount;
|
|||
|
unsigned int uiLetter = AnyLanguage_ReadCharFromString( psText, &iAdvanceCount, NULL );
|
|||
|
psText += iAdvanceCount;
|
|||
|
|
|||
|
switch (uiLetter)
|
|||
|
{
|
|||
|
case '^':
|
|||
|
if (*psText >= '0' &&
|
|||
|
*psText <= '9')
|
|||
|
{
|
|||
|
psText++;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
iCharCount++;
|
|||
|
}
|
|||
|
break; // colour code (note next-char skip)
|
|||
|
case 10: break; // linefeed
|
|||
|
case 13: break; // return
|
|||
|
case '_': iCharCount += (GetLanguageEnum() == eThai && (((unsigned char *)psText)[0] >= TIS_GLYPHS_START))?0:1; break; // special word-break hack
|
|||
|
default: iCharCount++; break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return iCharCount;
|
|||
|
}
|
|||
|
|
|||
|
int RE_Font_HeightPixels(const int iFontHandle, const float fScale)
|
|||
|
{
|
|||
|
CFontInfo *curfont;
|
|||
|
|
|||
|
curfont = GetFont(iFontHandle);
|
|||
|
if(curfont)
|
|||
|
{
|
|||
|
float fValue = curfont->GetPointSize() * fScale;
|
|||
|
return curfont->mbRoundCalcs ? Round(fValue) : fValue;
|
|||
|
}
|
|||
|
return(0);
|
|||
|
}
|
|||
|
|
|||
|
// iMaxPixelWidth is -1 for "all of string", else pixel display count...
|
|||
|
//
|
|||
|
void RE_Font_DrawString(int ox, int oy, const char *psText, const float *rgba, const int iFontHandle, int iMaxPixelWidth, const float fScale)
|
|||
|
{
|
|||
|
static qboolean gbInShadow = qfalse; // MUST default to this
|
|||
|
int x, y, colour, offset;
|
|||
|
const glyphInfo_t *pLetter;
|
|||
|
qhandle_t hShader;
|
|||
|
|
|||
|
assert (psText);
|
|||
|
|
|||
|
if(iFontHandle & STYLE_BLINK)
|
|||
|
{
|
|||
|
if((Sys_Milliseconds() >> 7) & 1)
|
|||
|
{
|
|||
|
return;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// // test code only
|
|||
|
// if (GetLanguageEnum() == eTaiwanese)
|
|||
|
// {
|
|||
|
// 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 (GetLanguageEnum() == eChinese)
|
|||
|
// {
|
|||
|
// //psText = "Ӷ<><D3B6>ս<EFBFBD><D5BD>II Լ<><D4BC>?Ī<><C4AA>˹ <20><><EFBFBD><EFBFBD>ʧ<EFBFBD><CAA7> <20><>Ҫ<EFBFBD><D2AA><EFBFBD>û<EFBFBD><C3BB><EFBFBD><EFBFBD>趨<EFBFBD>ı<EFBFBD><C4B1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Ԥ<><D4A4>,S3 ѹ<><D1B9>,DXT1 ѹ<><D1B9>,DXT5 ѹ<><D1B9>,16 Bit,32 Bit";
|
|||
|
// psText = "Ó¶<><D3B6>Õ½<EFBFBD><D5BD>II";
|
|||
|
// }
|
|||
|
// else
|
|||
|
// if (GetLanguageEnum() == eThai)
|
|||
|
// {
|
|||
|
// //psText = "<22>ҵðҹ<C3B0><D2B9>Ե<EFBFBD>ѳ<EFBFBD><D1B3><EFBFBD>ص<EFBFBD><D8B5>ˡ<EFBFBD><CBA1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ѻ<EFBFBD>ѡ<EFBFBD><D1A1><EFBFBD><EFBFBD><EFBFBD>·<EFBFBD><C2B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ѻ<EFBFBD><D1BA><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>";
|
|||
|
// psText = "<22>ҵðҹ<C3B0><D2B9>Ե";
|
|||
|
// psText = "<22><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ѻ";
|
|||
|
// psText = "<22><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ѻ <20><>_<EFBFBD>Թ_<D4B9><5F><EFBFBD><EFBFBD><EFBFBD>_1415";
|
|||
|
// }
|
|||
|
// else
|
|||
|
// if (GetLanguageEnum() == eKorean)
|
|||
|
// {
|
|||
|
// 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>.";
|
|||
|
// }
|
|||
|
// else
|
|||
|
// if (GetLanguageEnum() == eJapanese)
|
|||
|
// {
|
|||
|
// static char sBlah[200];
|
|||
|
// sprintf(sBlah,va("%c%c%c%c%c%c%c%c",0x82,0xA9,0x82,0xC8,0x8A,0xBF,0x8E,0x9A));
|
|||
|
// psText = &sBlah[0];
|
|||
|
// }
|
|||
|
// else
|
|||
|
// if (GetLanguageEnum() == eRussian)
|
|||
|
// {
|
|||
|
//// //psText = "<22><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>."
|
|||
|
// psText = "<22><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>";
|
|||
|
// }
|
|||
|
// else
|
|||
|
// if (GetLanguageEnum() == ePolish)
|
|||
|
// {
|
|||
|
// psText = "za<7A>o<EFBFBD>ony w 1364 roku, jest najstarsz<73> polsk<73> uczelni<6E> i nale<6C>y...";
|
|||
|
// psText = "za<7A>o<EFBFBD>ony nale<6C>y";
|
|||
|
// }
|
|||
|
|
|||
|
|
|||
|
CFontInfo *curfont = GetFont(iFontHandle);
|
|||
|
if(!curfont || !psText)
|
|||
|
{
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
float fScaleA = fScale;
|
|||
|
int iAsianYAdjust = 0;
|
|||
|
if (Language_IsAsian() && fScale > 0.7f)
|
|||
|
{
|
|||
|
fScaleA = fScale * 0.75f;
|
|||
|
iAsianYAdjust = /*Round*/((((float)curfont->GetPointSize() * fScale) - ((float)curfont->GetPointSize() * fScaleA))/2);
|
|||
|
}
|
|||
|
|
|||
|
// Draw a dropshadow if required
|
|||
|
if(iFontHandle & STYLE_DROPSHADOW)
|
|||
|
{
|
|||
|
offset = Round(curfont->GetPointSize() * fScale * 0.075f);
|
|||
|
|
|||
|
static const vec4_t v4DKGREY2 = {0.15f, 0.15f, 0.15f, 1};
|
|||
|
|
|||
|
gbInShadow = qtrue;
|
|||
|
RE_Font_DrawString(ox + offset, oy + offset, psText, v4DKGREY2, iFontHandle & SET_MASK, iMaxPixelWidth, fScale);
|
|||
|
gbInShadow = qfalse;
|
|||
|
}
|
|||
|
|
|||
|
RE_SetColor( rgba );
|
|||
|
|
|||
|
x = ox;
|
|||
|
oy += Round((curfont->GetHeight() - (curfont->GetDescender() >> 1)) * fScale);
|
|||
|
|
|||
|
qboolean bNextTextWouldOverflow = qfalse;
|
|||
|
while (*psText && !bNextTextWouldOverflow)
|
|||
|
{
|
|||
|
int iAdvanceCount;
|
|||
|
unsigned int uiLetter = AnyLanguage_ReadCharFromString( psText, &iAdvanceCount, NULL );
|
|||
|
psText += iAdvanceCount;
|
|||
|
|
|||
|
switch( uiLetter )
|
|||
|
{
|
|||
|
case 10: //linefeed
|
|||
|
x = ox;
|
|||
|
oy += Round(curfont->GetPointSize() * fScale);
|
|||
|
if (Language_IsAsian())
|
|||
|
{
|
|||
|
oy += 4; // this only comes into effect when playing in asian for "A long time ago in a galaxy" etc, all other text is line-broken in feeder functions
|
|||
|
}
|
|||
|
break;
|
|||
|
case 13: // Return
|
|||
|
break;
|
|||
|
case 32: // Space
|
|||
|
pLetter = curfont->GetLetter(' ');
|
|||
|
x += Round(pLetter->horizAdvance * fScale);
|
|||
|
bNextTextWouldOverflow = ( iMaxPixelWidth != -1 && ((x-ox)>iMaxPixelWidth) ) ? qtrue : qfalse; // yeuch
|
|||
|
break;
|
|||
|
case '_': // has a special word-break usage if in Thai (and followed by a thai char), and should not be displayed, else treat as normal
|
|||
|
if (GetLanguageEnum()== eThai && ((unsigned char *)psText)[0] >= TIS_GLYPHS_START)
|
|||
|
{
|
|||
|
break;
|
|||
|
}
|
|||
|
// else drop through and display as normal...
|
|||
|
case '^':
|
|||
|
if (uiLetter != '_') // necessary because of fallthrough above
|
|||
|
{
|
|||
|
if (*psText >= '0' &&
|
|||
|
*psText <= '9')
|
|||
|
{
|
|||
|
colour = ColorIndex(*psText++);
|
|||
|
if (!gbInShadow)
|
|||
|
{
|
|||
|
RE_SetColor( g_color_table[colour] );
|
|||
|
}
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
//purposely falls thrugh
|
|||
|
default:
|
|||
|
pLetter = curfont->GetLetter( uiLetter, &hShader ); // Description of pLetter
|
|||
|
if(!pLetter->width)
|
|||
|
{
|
|||
|
pLetter = curfont->GetLetter('.');
|
|||
|
}
|
|||
|
|
|||
|
float fThisScale = uiLetter > g_iNonScaledCharRange ? fScaleA : fScale;
|
|||
|
|
|||
|
// sigh, super-language-specific hack...
|
|||
|
//
|
|||
|
if (uiLetter == TIS_SARA_AM && GetLanguageEnum() == eThai)
|
|||
|
{
|
|||
|
x -= Round(7 * fThisScale);
|
|||
|
}
|
|||
|
|
|||
|
int iAdvancePixels = Round(pLetter->horizAdvance * fThisScale);
|
|||
|
bNextTextWouldOverflow = ( iMaxPixelWidth != -1 && (((x+iAdvancePixels)-ox)>iMaxPixelWidth) ) ? qtrue : qfalse; // yeuch
|
|||
|
if (!bNextTextWouldOverflow)
|
|||
|
{
|
|||
|
// this 'mbRoundCalcs' stuff is crap, but the only way to make the font code work. Sigh...
|
|||
|
//
|
|||
|
y = oy - (curfont->mbRoundCalcs ? Round(pLetter->baseline * fThisScale) : pLetter->baseline * fThisScale);
|
|||
|
if (curfont->m_fAltSBCSFontScaleFactor != -1)
|
|||
|
{
|
|||
|
y+=3; // I'm sick and tired of going round in circles trying to do this legally, so bollocks to it
|
|||
|
}
|
|||
|
|
|||
|
RE_StretchPic ( x + Round(pLetter->horizOffset * fScale), // float x
|
|||
|
(uiLetter > g_iNonScaledCharRange) ? y - iAsianYAdjust : y, // float y
|
|||
|
curfont->mbRoundCalcs ? Round(pLetter->width * fThisScale) : pLetter->width * fThisScale, // float w
|
|||
|
curfont->mbRoundCalcs ? Round(pLetter->height * fThisScale) : pLetter->height * fThisScale, // 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 += iAdvancePixels;
|
|||
|
}
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
//let it remember the old color //RE_SetColor(NULL);;
|
|||
|
}
|
|||
|
|
|||
|
int RE_RegisterFont(const char *psName)
|
|||
|
{
|
|||
|
FontIndexMap_t::iterator it = g_mapFontIndexes.find(psName);
|
|||
|
if (it != g_mapFontIndexes.end() )
|
|||
|
{
|
|||
|
int iFontIndex = (*it).second;
|
|||
|
return iFontIndex;
|
|||
|
}
|
|||
|
|
|||
|
// not registered, so...
|
|||
|
//
|
|||
|
{
|
|||
|
CFontInfo *pFont = new CFontInfo(psName);
|
|||
|
if (pFont->GetPointSize() > 0)
|
|||
|
{
|
|||
|
int iFontIndex = g_iCurrentFontIndex - 1;
|
|||
|
g_mapFontIndexes[psName] = iFontIndex;
|
|||
|
pFont->m_iThisFont = iFontIndex;
|
|||
|
return iFontIndex;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
g_mapFontIndexes[psName] = 0; // missing/invalid
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
void R_InitFonts(void)
|
|||
|
{
|
|||
|
g_iCurrentFontIndex = 1; // entry 0 is reserved for "missing/invalid"
|
|||
|
g_iNonScaledCharRange = INT_MAX; // default all chars to have no special scaling (other than user supplied)
|
|||
|
}
|
|||
|
|
|||
|
void R_ShutdownFonts(void)
|
|||
|
{
|
|||
|
for(int i = 1; i < g_iCurrentFontIndex; i++) // entry 0 is reserved for "missing/invalid"
|
|||
|
{
|
|||
|
delete g_vFontArray[i];
|
|||
|
}
|
|||
|
g_mapFontIndexes.clear();
|
|||
|
g_vFontArray.clear();
|
|||
|
g_iCurrentFontIndex = 1; // entry 0 is reserved for "missing/invalid"
|
|||
|
|
|||
|
g_ThaiCodes.Clear();
|
|||
|
}
|
|||
|
|
|||
|
// this is only really for debugging while tinkering with fonts, but harmless to leave in...
|
|||
|
//
|
|||
|
void R_ReloadFonts_f(void)
|
|||
|
{
|
|||
|
// first, grab all the currently-registered fonts IN THE ORDER THEY WERE REGISTERED...
|
|||
|
//
|
|||
|
vector <sstring_t> vstrFonts;
|
|||
|
|
|||
|
for (int iFontToFind = 1; iFontToFind < g_iCurrentFontIndex; iFontToFind++)
|
|||
|
{
|
|||
|
for (FontIndexMap_t::iterator it = g_mapFontIndexes.begin(); it != g_mapFontIndexes.end(); ++it)
|
|||
|
{
|
|||
|
if (iFontToFind == (*it).second)
|
|||
|
{
|
|||
|
vstrFonts.push_back( (*it).first );
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
if ( it == g_mapFontIndexes.end() )
|
|||
|
{
|
|||
|
break; // couldn't find this font
|
|||
|
}
|
|||
|
}
|
|||
|
if ( iFontToFind == g_iCurrentFontIndex ) // found all of them?
|
|||
|
{
|
|||
|
// now restart the font system...
|
|||
|
//
|
|||
|
R_ShutdownFonts();
|
|||
|
R_InitFonts();
|
|||
|
//
|
|||
|
// and re-register our fonts in the same order as before (note that some menu items etc cache the string lengths so really a vid_restart is better, but this is just for my testing)
|
|||
|
//
|
|||
|
for (int iFont = 0; iFont < vstrFonts.size(); iFont++)
|
|||
|
{
|
|||
|
#ifdef _DEBUG
|
|||
|
int iNewFontHandle = RE_RegisterFont( vstrFonts[iFont].c_str() );
|
|||
|
assert( iNewFontHandle == iFont+1 );
|
|||
|
#else
|
|||
|
RE_RegisterFont( vstrFonts[iFont].c_str() );
|
|||
|
#endif
|
|||
|
}
|
|||
|
Com_Printf( "Done.\n" );
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
Com_Printf( "Problem encountered finding current fonts, ignoring.\n" ); // poo. Oh well, forget it.
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
// end
|