/* cl_font.c - bare bones engine font manager Copyright (C) 2023 Alibek Omarov This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #include "common.h" #include "filesystem.h" #include "client.h" #include "qfont.h" qboolean CL_FixedFont( cl_font_t *font ) { return font && font->valid && font->type == FONT_FIXED; } static int CL_LoadFontTexture( const char *fontname, uint texFlags, int *width ) { int font_width; int tex; if( !g_fsapi.FileExists( fontname, false )) return 0; tex = ref.dllFuncs.GL_LoadTexture( fontname, NULL, 0, texFlags ); if( !tex ) return 0; font_width = REF_GET_PARM( PARM_TEX_WIDTH, tex ); if( !font_width ) { ref.dllFuncs.GL_FreeTexture( tex ); return 0; } *width = font_width; return tex; } qboolean Con_LoadFixedWidthFont( const char *fontname, cl_font_t *font, float scale, int rendermode, uint texFlags ) { int font_width, i; if( font->valid ) return true; // already loaded font->hFontTexture = CL_LoadFontTexture( fontname, texFlags, &font_width ); if( !font->hFontTexture ) return false; font->type = FONT_FIXED; font->valid = true; font->scale = scale; font->nearest = FBitSet( texFlags, TF_NEAREST ); font->rendermode = rendermode; font->charHeight = Q_rint( font_width / 16 * scale ); for( i = 0; i < ARRAYSIZE( font->fontRc ); i++ ) { font->fontRc[i].left = ( i * font_width / 16 ) % font_width; font->fontRc[i].right = font->fontRc[i].left + font_width / 16; font->fontRc[i].top = ( i / 16 ) * ( font_width / 16 ); font->fontRc[i].bottom = font->fontRc[i].top + font_width / 16; font->charWidths[i] = Q_rint( font_width / 16 * scale ); } return true; } qboolean Con_LoadVariableWidthFont( const char *fontname, cl_font_t *font, float scale, int rendermode, uint texFlags ) { fs_offset_t length; qfont_t src; byte *pfile; int font_width, i; if( font->valid ) return true; pfile = g_fsapi.LoadFile( fontname, &length, false ); if( !pfile ) return false; if( length < sizeof( src )) { Mem_Free( pfile ); return false; } memcpy( &src, pfile, sizeof( src )); Mem_Free( pfile ); font->hFontTexture = CL_LoadFontTexture( fontname, texFlags, &font_width ); if( !font->hFontTexture ) return false; font->type = FONT_VARIABLE; font->valid = true; font->scale = scale; font->nearest = FBitSet( texFlags, TF_NEAREST ); font->rendermode = rendermode; font->charHeight = Q_rint( src.rowheight * scale ); for( i = 0; i < ARRAYSIZE( font->fontRc ); i++ ) { const charinfo *ci = &src.fontinfo[i]; font->fontRc[i].left = (word)ci->startoffset % font_width; font->fontRc[i].right = font->fontRc[i].left + ci->charwidth; font->fontRc[i].top = (word)ci->startoffset / font_width; font->fontRc[i].bottom = font->fontRc[i].top + src.rowheight; font->charWidths[i] = Q_rint( src.fontinfo[i].charwidth * scale ); } return true; } void CL_FreeFont( cl_font_t *font ) { if( !font || !font->valid ) return; ref.dllFuncs.GL_FreeTexture( font->hFontTexture ); memset( font, 0, sizeof( *font )); } static int CL_CalcTabStop( const cl_font_t *font, int x ) { int space = font->charWidths[' ']; int tab = space * 6; // 6 spaces int stop = tab - x % tab; if( stop < space ) return tab * 2 - x % tab; // select next return stop; } int CL_DrawCharacter( float x, float y, int number, rgba_t color, cl_font_t *font, int flags ) { wrect_t *rc; float w, h; float s1, t1, s2, t2, half = 0.5f; int texw, texh; if( !font || !font->valid || y < -font->charHeight ) return 0; // check if printable if( number <= 32 ) { if( number == ' ' ) return font->charWidths[' ']; else if( number == '\t' ) return CL_CalcTabStop( font, x ); return 0; } if( FBitSet( flags, FONT_DRAW_UTF8 )) number = Con_UtfProcessChar( number & 255 ); else number &= 255; if( !number || !font->charWidths[number]) return 0; R_GetTextureParms( &texw, &texh, font->hFontTexture ); if( !texw || !texh ) return font->charWidths[number]; rc = &font->fontRc[number]; if( font->nearest || font->scale <= 1.0f ) half = 0; s1 = ((float)rc->left + half ) / texw; t1 = ((float)rc->top + half ) / texh; s2 = ((float)rc->right - half ) / texw; t2 = ((float)rc->bottom - half ) / texh; w = ( rc->right - rc->left ) * font->scale; h = ( rc->bottom - rc->top ) * font->scale; if( FBitSet( flags, FONT_DRAW_HUD )) SPR_AdjustSize( &x, &y, &w, &h ); if( !FBitSet( flags, FONT_DRAW_NORENDERMODE )) ref.dllFuncs.GL_SetRenderMode( font->rendermode ); // don't apply color to fixed fonts it's already colored if( font->type != FONT_FIXED || REF_GET_PARM( PARM_TEX_GLFORMAT, font->hFontTexture ) == 0x8045 ) // GL_LUMINANCE8_ALPHA8 ref.dllFuncs.Color4ub( color[0], color[1], color[2], color[3] ); else ref.dllFuncs.Color4ub( 255, 255, 255, color[3] ); ref.dllFuncs.R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, font->hFontTexture ); return font->charWidths[number]; } int CL_DrawString( float x, float y, const char *s, rgba_t color, cl_font_t *font, int flags ) { rgba_t current_color; int draw_len = 0; if( !font || !font->valid ) return 0; if( FBitSet( flags, FONT_DRAW_UTF8 )) Con_UtfProcessChar( 0 ); // clear utf state if( !FBitSet( flags, FONT_DRAW_NORENDERMODE )) ref.dllFuncs.GL_SetRenderMode( font->rendermode ); Vector4Copy( color, current_color ); while( *s ) { if( *s == '\n' ) { s++; if( !*s ) break; // some client functions ignore newlines if( !FBitSet( flags, FONT_DRAW_NOLF )) { draw_len = 0; y += font->charHeight; } if( FBitSet( flags, FONT_DRAW_RESETCOLORONLF )) Vector4Copy( color, current_color ); continue; } if( IsColorString( s )) { // don't copy alpha if( !FBitSet( flags, FONT_DRAW_FORCECOL )) VectorCopy( g_color_table[ColorIndex(*( s + 1 ))], current_color ); s += 2; continue; } // 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 ); s++; } return draw_len; } int CL_DrawStringf( cl_font_t *font, float x, float y, rgba_t color, int flags, const char *fmt, ... ) { va_list va; char buf[MAX_VA_STRING]; va_start( va, fmt ); Q_vsnprintf( buf, sizeof( buf ), fmt, va ); va_end( va ); return CL_DrawString( x, y, buf, color, font, flags ); } void CL_DrawCharacterLen( cl_font_t *font, int number, int *width, int *height ) { if( !font || !font->valid ) return; if( width ) { if( number == '\t' ) *width = CL_CalcTabStop( font, 0 ); // at least return max tabstop else *width = font->charWidths[number & 255]; } if( height ) *height = font->charHeight; } void CL_DrawStringLen( cl_font_t *font, const char *s, int *width, int *height, int flags ) { int draw_len = 0; if( !font || !font->valid ) return; if( height ) *height = font->charHeight; if( width ) *width = 0; if( !COM_CheckString( s )) return; if( FBitSet( flags, FONT_DRAW_UTF8 )) Con_UtfProcessChar( 0 ); // reset utf state while( *s ) { int number; if( *s == '\n' ) { // BUG: no check for end string here // but high chances somebody's relying on this s++; draw_len = 0; if( !FBitSet( flags, FONT_DRAW_NOLF )) { if( height ) *height += font->charHeight; } continue; } else if( *s == '\t' ) { draw_len += CL_CalcTabStop( font, 0 ); // at least return max tabstop s++; continue; } if( IsColorString( s )) { s += 2; continue; } if( FBitSet( flags, FONT_DRAW_UTF8 )) number = Con_UtfProcessChar( (byte)*s ); else number = (byte)*s; if( number ) { draw_len += font->charWidths[number]; if( draw_len > *width ) *width = draw_len; } s++; } }