Skip to content

Commit

Permalink
Multilingual font rendering
Browse files Browse the repository at this point in the history

put your ttf file into "3rdparty\extras\xash-extras\gfx\fonts\"

change cl_font.c
FONT_COUNT
g_FontsPath
g_FontsSize
  • Loading branch information
YuanBaian committed Feb 1, 2025
1 parent 5378494 commit eea68ac
Show file tree
Hide file tree
Showing 5 changed files with 5,388 additions and 17 deletions.
182 changes: 179 additions & 3 deletions engine/client/cl_font.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ GNU General Public License for more details.
#include "filesystem.h"
#include "client.h"
#include "qfont.h"
#define STB_TRUETYPE_IMPLEMENTATION
#define STBTT_STATIC
#include "cl_font.h"

#define FONT_COUNT 2

static Font* g_currentFont;
static Font g_Fonts[FONT_COUNT];
const static string g_FontsPath[FONT_COUNT] = {"gfx/fonts/tahoma.ttf", "gfx/fonts/FiraSans-Regular.ttf"};
const static int g_FontsSize[FONT_COUNT] = {14, 14};
const static rgba_t nullColor = { 255, 255, 255, 255 };

qboolean CL_FixedFont( cl_font_t *font )
{
Expand All @@ -43,6 +54,15 @@ static int CL_LoadFontTexture( const char *fontname, uint texFlags, int *width )
}

*width = font_width;

for (int i = 0; i < FONT_COUNT; i++)
{
if (Font_Init( &g_Fonts[i], g_FontsPath[i], g_FontsSize[i] ))
Con_Printf( "Unable to create font %s\n", g_FontsPath[i] );
Font_SetWidth(&g_Fonts[i], g_FontsSize[i]);
}

g_currentFont = &g_Fonts[0];
return tex;
}

Expand Down Expand Up @@ -175,6 +195,9 @@ static int CL_CalcTabStop( const cl_font_t *font, int x )

int CL_DrawCharacter( float x, float y, int number, rgba_t color, cl_font_t *font, int flags )
{

return Font_DrawChar(font, color, x, y, number, flags);

wrect_t *rc;
float w, h;
float s1, t1, s2, t2, half = 0.5f;
Expand Down Expand Up @@ -278,8 +301,11 @@ int CL_DrawString( float x, float y, const char *s, rgba_t color, cl_font_t *fon
continue;
}

int ch = Con_UtfProcessChar( (unsigned char)*s );

// skip setting rendermode, it was changed for this string already
draw_len += CL_DrawCharacter( x + draw_len, y, (byte)*s, current_color, font, flags | FONT_DRAW_NORENDERMODE );
if( ch )
draw_len += CL_DrawCharacter( x + draw_len, y, ch, current_color, font, flags | FONT_DRAW_NORENDERMODE );

s++;
}
Expand All @@ -306,7 +332,7 @@ void CL_DrawCharacterLen( cl_font_t *font, int number, int *width, int *height )
{
if( number == '\t' )
*width = CL_CalcTabStop( font, 0 ); // at least return max tabstop
else *width = font->charWidths[number & 255];
else *width = Font_DrawChar(font, nullColor, 0, 0, number, 0);//font->charWidths[number & 255];
}
if( height ) *height = font->charHeight;
}
Expand Down Expand Up @@ -366,7 +392,7 @@ void CL_DrawStringLen( cl_font_t *font, const char *s, int *width, int *height,

if( number )
{
draw_len += font->charWidths[number];
draw_len += Font_DrawChar(font, nullColor, 0, 0, number, 0);//font->charWidths[number];

if( width )
{
Expand All @@ -378,3 +404,153 @@ void CL_DrawStringLen( cl_font_t *font, const char *s, int *width, int *height,
s++;
}
}

/* Font Class */

qboolean Font_Init(Font* self, char* name, int tall)
{
if( !self )
return false;

char font_face_path[256];

int len = 0;
self->m_pFontData = COM_LoadFileForMe( name, &len );

if( !stbtt_InitFont( &self->m_fontInfo, self->m_pFontData, 0 ) )
return false;

self->scale = stbtt_ScaleForPixelHeightPrecision( &self->m_fontInfo, tall + 4 );
int x0, y0, x1, y1;

stbtt_GetFontVMetrics( &self->m_fontInfo, &self->m_iAscent, NULL, NULL );
self->m_iAscent = round( self->m_iAscent * self->scale );

stbtt_GetFontBoundingBox( &self->m_fontInfo, &x0, &y0, &x1, &y1 );
self->m_iHeight = round(( y1 - y0 ) * self->scale ); // maybe wrong!
self->m_iMaxCharWidth = round(( x1 - x0 ) * self->scale ); // maybe wrong!

return true;
}

int Font_CheckCharExists(Font* self, int ch)
{
for (int i = 0; i < self->m_iCharCount; i++)
{
if (65536 * self->m_iWidth + ch == self->m_iBuffer[i])
return i;
}

return -1;
}


int Font_LoadChar(Font* self, int ch)
{
int iCallBack = Font_CheckCharExists(self, ch);
if (iCallBack > -1) return iCallBack;

byte *buf, *dst;
int bm_top, bm_left, bm_rows, bm_width;
buf = stbtt_GetCodepointBitmap( &self->m_fontInfo, self->scale, self->scale, ch, &bm_width, &bm_rows, &bm_left, &bm_top );
// buf = stbtt_GetCodepointSDF( &self->m_fontInfo, self->scale, ch, 1, 90, 90.0f, &bm_width, &bm_rows, &bm_left, &bm_top );
int id;
char texname[30];

Q_snprintf( texname, sizeof( texname ), "font_%i", 65536 * self->m_iWidth + ch);

if (!(id = ref.dllFuncs.GL_FindTexture(texname)))
{
byte *rgbData = malloc(bm_rows * bm_width * 4);
for (int i = 0; i < bm_rows; i++)
{
for (int j = 0; j < bm_width; j++)
{
unsigned int index = i * bm_width + j;
unsigned char pixel_value = buf[index];
int rgbIndex = (i * bm_width + j) * 4;
rgbData[rgbIndex + 0] = 255;
rgbData[rgbIndex + 1] = 255;
rgbData[rgbIndex + 2] = 255;
rgbData[rgbIndex + 3] = pixel_value;//(pixel_value > 90) ? 255 : 0;
}
}

id = ref.dllFuncs.GL_CreateTexture(texname, bm_width, bm_rows, rgbData, TF_IMAGE | TF_HAS_ALPHA);

free(rgbData);
}

self->m_tFontTexture[self->m_iCharCount].m_iTexture = id;
self->m_tFontTexture[self->m_iCharCount].m_iXOff = bm_left;
self->m_tFontTexture[self->m_iCharCount].m_iYOff = bm_top;
self->m_tFontTexture[self->m_iCharCount].m_iWidth = bm_width;
self->m_tFontTexture[self->m_iCharCount].m_iHeight = bm_rows;
self->m_tFontTexture[self->m_iCharCount].Char = ch;
self->m_tFontTexture[self->m_iCharCount].pTexture = buf;
// Font_AddCharInToPage(self, &self->m_tFontTexture[self->m_iCharCount]);

self->m_iBuffer[self->m_iCharCount] = 65536 * self->m_iWidth + ch;
self->m_iCharCount++;
return (self->m_iCharCount - 1);
}

CHARINFO* Font_GetChar(Font* self, int ch)
{
return &self->m_tFontTexture[Font_LoadChar(self, ch)];
}

int Font_DrawChar(cl_font_t *font, rgba_t color, int x, int y, int number, int flags)
{
if (stbtt_FindGlyphIndex( &g_currentFont->m_fontInfo, number ) == 0)
{
for (int i = 0; i < FONT_COUNT; i++)
{
if (stbtt_FindGlyphIndex( &g_Fonts[i].m_fontInfo, number ) == 0)
continue;
else
break;
}
return 0;
}
// check if printable
if( number <= 32 )
{
if( number == ' ' )
return font->charWidths[' '];
else if( number == '\t' )
return CL_CalcTabStop( font, x );
return 0;
}
CHARINFO* pCharInfo = Font_GetChar(g_currentFont, number);

if( !FBitSet( flags, FONT_DRAW_NORENDERMODE ))
CL_SetFontRendermode( font );

if( font->type != FONT_FIXED || REF_GET_PARM( PARM_TEX_GLFORMAT, font->hFontTexture ) == 0x8045 ) // GL_LUMINANCE8_ALPHA8
{
//outline
ref.dllFuncs.Color4ub( 1, 1, 1, 255 );
ref.dllFuncs.R_DrawStretchPic( x + pCharInfo->m_iXOff + 1, y + pCharInfo->m_iYOff + 1, pCharInfo->m_iWidth, pCharInfo->m_iHeight, 0.0f, 0.0f, 1.0f, 1.0f, pCharInfo->m_iTexture);
ref.dllFuncs.R_DrawStretchPic( x + pCharInfo->m_iXOff + 1, y + pCharInfo->m_iYOff + 1 + 1, pCharInfo->m_iWidth, pCharInfo->m_iHeight, 0.0f, 0.0f, 1.0f, 1.0f, pCharInfo->m_iTexture);
//normal
ref.dllFuncs.Color4ub( color[0], color[1], color[2], color[3] );
ref.dllFuncs.R_DrawStretchPic( x + pCharInfo->m_iXOff, y + pCharInfo->m_iYOff, pCharInfo->m_iWidth, pCharInfo->m_iHeight, 0.0f, 0.0f, 1.0f, 1.0f, pCharInfo->m_iTexture);
//bold
ref.dllFuncs.R_DrawStretchPic( x + pCharInfo->m_iXOff + 1, y + pCharInfo->m_iYOff, pCharInfo->m_iWidth, pCharInfo->m_iHeight, 0.0f, 0.0f, 1.0f, 1.0f, pCharInfo->m_iTexture);

}
else
{
ref.dllFuncs.Color4ub( 255, 255, 255, color[3] );
ref.dllFuncs.R_DrawStretchPic( x + pCharInfo->m_iXOff, y + pCharInfo->m_iYOff, pCharInfo->m_iWidth, pCharInfo->m_iHeight, 0.0f, 0.0f, 1.0f, 1.0f, pCharInfo->m_iTexture);
}

return pCharInfo->m_iWidth + pCharInfo->m_iXOff;

}

void Font_SetWidth(Font* self, int iWidth)
{
self->m_iWidth = self->m_iHeight = iWidth;
}
50 changes: 50 additions & 0 deletions engine/client/cl_font.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* cl_font.h
*
* This file is part of the Xash3D FWGS project.
*
* Xash3D FWGS is a fork of the Xash3D Engine by Unkle Mike.
*
* This header file contains the declarations for font handling in the client.
*
* Author: [Your Name]
* Date: [Current Date]
*/

#ifndef CL_FONT_H
#define CL_FONT_H

#include "stb_truetype.h"

#define FONTS_MAX_BUFFER 1000
#define FONT_PAGE_MAX 8

typedef struct {
int m_iTexture;
int m_iWidth,m_iHeight;
int m_iXOff,m_iYOff;
BYTE *pTexture;
int m_iCharWidth;
int Char;
} CHARINFO;

typedef struct {
byte *m_pFontData;
stbtt_fontinfo m_fontInfo;

double scale;

int m_iAscent, m_iMaxCharWidth;
int m_iCharCount;
int m_iWidth, m_iHeight;
int m_iBuffer[FONTS_MAX_BUFFER];
CHARINFO m_tFontTexture[FONTS_MAX_BUFFER];
} Font;

int Font_Init(Font* self, char* name);
int Font_CheckCharExists(Font* self, int ch);
int Font_LoadChar(Font* self, int ch);
CHARINFO* Font_GetChar(Font* self, int ch);
int Font_DrawChar(cl_font_t *font, rgba_t color, int x, int y, int number, int flags);
void Font_SetWidth(Font* self, int iWidth);
#endif // CL_FONT_H
17 changes: 11 additions & 6 deletions engine/client/cl_game.c
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,8 @@ void CL_DrawCenterPrint( void )
char *pText;
int i, j, x, y;
int width, lineLength;
byte *colorDefault, line[MAX_LINELENGTH];
byte *colorDefault;
int line[MAX_LINELENGTH];
int charWidth, charHeight;

if( !clgame.centerPrint.time )
Expand All @@ -422,18 +423,22 @@ void CL_DrawCenterPrint( void )

CL_DrawCharacterLen( font, 0, NULL, &charHeight );
CL_SetFontRendermode( font );
Con_UtfProcessChar( 0 );
for( i = 0; i < clgame.centerPrint.lines; i++ )
{
lineLength = 0;
width = 0;

while( *pText && *pText != '\n' && lineLength < MAX_LINELENGTH )
{
byte c = *pText;
line[lineLength] = c;
CL_DrawCharacterLen( font, c, &charWidth, NULL );
width += charWidth;
lineLength++;
int ch = Con_UtfProcessChar( (unsigned char)*pText );
if ( ch )
{
CL_DrawCharacterLen( font, ch, &charWidth, NULL );
line[lineLength] = ch;
width += charWidth;
lineLength++;
}
pText++;
}

Expand Down
70 changes: 62 additions & 8 deletions engine/client/console.c
Original file line number Diff line number Diff line change
Expand Up @@ -619,17 +619,71 @@ Convert utf char to current font's single-byte encoding
*/
int Con_UtfProcessCharForce( int in )
{
// TODO: get rid of global state where possible
static utfstate_t state = { 0 };
static int m = -1, k = 0; //multibyte state
static int uc = 0; //unicode char

int ch = Q_DecodeUTF8( &state, in );
if( !in )
{
m = -1;
k = 0;
uc = 0;
return 0;
}

// Get character length
if(m == -1)
{
uc = 0;
if( in >= 0xF8 )
{
return 0;
}
else if( in >= 0xF0 )
{
uc = in & 0x07;
m = 3;
}
else if( in >= 0xE0 )
{
uc = in & 0x0F;
m = 2;
}
else if( in >= 0xC0 )
{
uc = in & 0x1F;
m = 1;
}
else if( in <= 0x7F )
{
return in; // ascii
}
// return 0 if we need more chars to decode one
k = 0;
return 0;
}
// get more chars
else if( k <= m )
{
uc <<= 6;
uc += in & 0x3F;
k++;
}
if( in > 0xBF || m < 0 )
{
m = -1;
return 0;
}

if( k == m )
{
k = m = -1;

if( g_codepage == 1251 )
return Q_UnicodeToCP1251( ch );
if( g_codepage == 1252 )
return Q_UnicodeToCP1252( ch );
return uc;

return '?'; // not implemented yet
// not implemented yet
// return '?';
}
return 0;
}

int GAME_EXPORT Con_UtfProcessChar( int in )
Expand Down
Loading

0 comments on commit eea68ac

Please sign in to comment.