/* =========================================================================== Copyright (C) 2024 the OpenMoHAA team This file is part of OpenMoHAA source code. OpenMoHAA source code 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 2 of the License, or (at your option) any later version. OpenMoHAA source code 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. You should have received a copy of the GNU General Public License along with OpenMoHAA source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // tr_font.cpp -- font rendering #include "tr_local.h" #define MAX_LOADED_FONTS 255 static fontheader_sgl_t s_loadedFonts_sgl[MAX_LOADED_FONTS]; static int s_numLoadedFonts_sgl = 0; static fontheader_t s_loadedFonts[MAX_LOADED_FONTS]; static int s_numLoadedFonts = 0; static float s_fontHeightScale = 1.0; static float s_fontGeneralScale = 1.0; static float s_fontZ = 0.0; void R_ShutdownFont() { int i; fontheader_t *header; fontheader_sgl_t *header_sgl; for (i = 0; i < s_numLoadedFonts; i++) { header = &s_loadedFonts[i]; if (header->charTable) { ri.Free(header->charTable); header->charTable = NULL; } memset(header, 0, sizeof(*header)); } for (i = 0; i < s_numLoadedFonts_sgl; i++) { header_sgl = &s_loadedFonts_sgl[i]; memset(header_sgl, 0, sizeof(*header_sgl)); } } void R_SetFontHeightScale(float scale) { s_fontHeightScale = scale; } void R_SetFontScale(float scale) { s_fontGeneralScale = scale; } void R_SetFontZ(float zed) { s_fontZ = zed; } static int CodeSearch(const fontheader_t* font, unsigned short uch) { int mid; int l, r; r = font->charTableLength; l = 0; while (l < r) { mid = (l + r) / 2; if (font->charTable[mid].cp > uch) { r = (l + r) / 2; continue; } if (uch == font->charTable[mid].cp) { return (l + r) / 2; } l = mid + 1; } if (uch != font->charTable[l].cp) { return -1; } return l; } static qboolean DBCSIsLeadByte(const fontheader_t* font, unsigned short uch) { // Byte ranges found in Wikipedia articles with relevant search strings in each case switch (font->codePage) { case 932: // Shift_jis return ((uch >= 0x81) && (uch <= 0x9F)) || ((uch >= 0xE0) && (uch <= 0xFC)); // Lead bytes F0 to FC may be a Microsoft addition. case 936: // GBK return (uch >= 0x81) && (uch <= 0xFE); case 949: // Korean Wansung KS C-5601-1987 return (uch >= 0x81) && (uch <= 0xFE); case 950: // Big5 return (uch >= 0x81) && (uch <= 0xFE); case 1361: // Korean Johab KS C-5601-1992 return ((uch >= 0x84) && (uch <= 0xD3)) || ((uch >= 0xD8) && (uch <= 0xDE)) || ((uch >= 0xE0) && (uch <= 0xF9)); } return false; } fontheader_sgl_t* R_LoadFont_sgl(const char* name) { int i; char* theFile; fontheader_sgl_t* header; char* ref; const char* token; qboolean error; error = qfalse; for (i = 0; i < s_numLoadedFonts_sgl; i++) { header = &s_loadedFonts_sgl[i]; if (!Q_stricmp(name, header->name)) { return header; } } if (s_numLoadedFonts >= MAX_LOADED_FONTS) { ri.Printf(PRINT_WARNING, "LoadFont: Too many fonts loaded! Couldn't load %s\n", name); return NULL; } if (ri.FS_ReadFile(va("fonts/%s.RitualFont", name), (void**)&theFile) == -1) { ri.Printf(PRINT_WARNING, "LoadFont: Couldn't load font %s\n", name); return NULL; } header = &s_loadedFonts_sgl[s_numLoadedFonts_sgl]; header->height = 0.0; header->aspectRatio = 0.0; Q_strncpyz(header->name, name, sizeof(header->name)); ref = theFile; while (ref && !error) { token = COM_Parse(&ref); if (!Q_stricmp(token, "RitFont")) { // ignore this token continue; } if (!Q_stricmp(token, "indirections")) { token = COM_Parse(&ref); if (Q_stricmp(token, "{")) { error = qtrue; break; } for (i = 0; i < 256; i++) { token = COM_Parse(&ref); if (!token[0]) { error = qtrue; break; } header->indirection[i] = atoi(token); } if (error) { break; } token = COM_Parse(&ref); if (Q_stricmp(token, "}")) { error = qtrue; break; } } else if (!Q_stricmp(token, "locations")) { token = COM_Parse(&ref); if (Q_stricmp(token, "{")) { error = qtrue; break; } for (i = 0; i < 256; i++) { token = COM_Parse(&ref); if (Q_stricmp(token, "{")) { error = qtrue; break; } if (header->aspectRatio == 0.0) { ri.Printf(PRINT_WARNING, "WARNING: aspect decl must be before locations in font '%s'", name); break; } header->locations[i].pos[0] = atof(COM_Parse(&ref)) / 256.0; header->locations[i].pos[1] = atof(COM_Parse(&ref)) * header->aspectRatio / 256.0; header->locations[i].size[0] = atof(COM_Parse(&ref)) / 256.0; header->locations[i].size[1] = atof(COM_Parse(&ref)) * header->aspectRatio / 256.0; token = COM_Parse(&ref); if (Q_stricmp(token, "}")) { error = qtrue; break; } } if (error) { break; } token = COM_Parse(&ref); if (Q_stricmp(token, "}")) { error = qtrue; break; } } else if (!Q_stricmp(token, "height")) { // parse the header height token = COM_Parse(&ref); header->height = atof(token); } else if (!Q_stricmp(token, "aspect")) { // parse the aspect ratio token = COM_Parse(&ref); header->aspectRatio = atof(token); } else { // unknown token break; } } if (token[0]) { ri.Printf(PRINT_WARNING, "WARNING: Unknown token '%s' parsing font '%s'\n", token, name); error = qtrue; } R_LoadFontShader(header); if (!header->height || !header->aspectRatio) { // invalid height or aspect ratio error = qtrue; } ri.FS_FreeFile(theFile); if (error) { ri.Printf(3, "WARNING: Error parsing font %s.\n", name); return NULL; } else { s_numLoadedFonts_sgl++; return header; } return NULL; } fontheader_t* R_LoadFont(const char* name) { int i; char* theFile; fontheader_t* header; char* ref; const char* token; qboolean error; char* pRitFontNames[32]; error = qfalse; for (i = 0; i < s_numLoadedFonts; i++) { header = &s_loadedFonts[i]; if (!Q_stricmp(name, header->name)) { return header; } } if (s_numLoadedFonts >= MAX_LOADED_FONTS) { ri.Printf(PRINT_WARNING, "LoadFont: Too many fonts loaded! Couldn't load %s\n", name); return NULL; } if (ri.FS_ReadFile(va("fonts/%s.RitualFont", name), (void**)&theFile) == -1) { ri.Printf(PRINT_WARNING, "LoadFont: Couldn't load font %s\n", name); return NULL; } memset(pRitFontNames, 0, sizeof(pRitFontNames)); ref = theFile; header = &s_loadedFonts[s_numLoadedFonts]; token = COM_Parse(&ref); if (Q_stricmp(token, "RitFontList")) { if (Q_stricmp(token, "RitFont")) { ri.Printf(PRINT_WARNING, "LoadFont: Not actual font %s\n", name); return NULL; } header->numPages = 0; } else { while (ref && !error) { token = COM_Parse(&ref); if (!Q_stricmp(token, "CodePage")) { header->codePage = atoi(COM_Parse(&ref)); } else if (!Q_stricmp(token, "Chars")) { header->charTableLength = atoi(COM_Parse(&ref)); header->charTable = (fontchartable_t*)ri.Malloc(header->charTableLength * sizeof(fontchartable_t)); if (!header->charTable) { ri.Printf(PRINT_WARNING, "LoadFont: Couldn't alloc mem %s\n", name); error = qtrue; break; } } else if (!Q_stricmp(token, "Pages")) { header->numPages = atoi(COM_Parse(&ref)); } else if (!Q_stricmp(token, "RitFontName")) { token = COM_Parse(&ref); if (Q_stricmp(token, "{")) { ri.Printf(PRINT_WARNING, "LoadFont: Bad Format %s\n", name); error = qtrue; break; } for (i = 0; i < header->numPages; i++) { token = COM_Parse(&ref); if (!token[0]) { ri.Printf(PRINT_WARNING, "LoadFont: Bad Token %s\n", name); error = qtrue; break; } pRitFontNames[i] = (char*)ri.Malloc(strlen(token) + 1); if (!pRitFontNames[i]) { ri.Printf(PRINT_WARNING, "LoadFont: Couldn't alloc mem %s\n", name); error = qtrue; break; } strcpy(pRitFontNames[i], token); } if (error) { break; } token = COM_Parse(&ref); if (Q_stricmp(token, "}")) { ri.Printf(PRINT_WARNING, "LoadFont: Bad Format %s\n", name); error = qtrue; break; } } else if (!Q_stricmp(token, "CharTable")) { token = COM_Parse(&ref); if (Q_stricmp(token, "{")) { ri.Printf(PRINT_WARNING, "LoadFont: Bad Format %s\n", name); error = qtrue; break; } for (i = 0; i < header->charTableLength; i++) { token = COM_Parse(&ref); if (Q_stricmp(token, "{")) { ri.Printf(PRINT_WARNING, "LoadFont: Bad Token %s\n", name); error = qtrue; break; } header->charTable[i].cp = atoi(COM_Parse(&ref)); header->charTable[i].index = atoi(COM_Parse(&ref)); header->charTable[i].loc = atoi(COM_Parse(&ref)); atoi(COM_Parse(&ref)); token = COM_Parse(&ref); if (Q_stricmp(token, "}")) { ri.Printf(PRINT_WARNING, "LoadFont: Bad Format %s\n", name); error = qtrue; break; } } if (error) { break; } token = COM_Parse(&ref); if (Q_stricmp(token, "}")) { ri.Printf(PRINT_WARNING, "LoadFont: Bad Format %s\n", name); error = qtrue; break; } } else { ri.Printf(PRINT_WARNING, "LoadFont: Bad Token %s\n", name); error = qtrue; break; } } } ri.FS_FreeFile(theFile); if (!header->numPages) { header->charTableLength = 0; header->charTable = NULL; header->sgl[0] = R_LoadFont_sgl(name); if (!header->sgl[0]) { ri.Printf(PRINT_WARNING, "LoadFont: failed %s\n", name); return NULL; } } else { for (i = 0; i < header->numPages; i++) { header->sgl[i] = R_LoadFont_sgl(pRitFontNames[i]); if (!header->sgl[i]) { ri.Printf(PRINT_WARNING, "LoadFont: failed %s(%s)\n", pRitFontNames[i], name); error = qtrue; break; } } // Free all allocated strings for (i = 0; i < header->numPages; i++) { ri.Free(pRitFontNames[i]); } if (error) { if (header->charTable) { ri.Free(header->charTable); } header->numPages = 0; header->charTableLength = 0; header->charTable = NULL; } } strcpy(header->name, name); s_numLoadedFonts++; return header; } void R_LoadFontShader(fontheader_sgl_t* font) { int i; int save; char filename[64]; shader_t* fontshader; save = r_sequencenumber; r_sequencenumber = -1; Com_sprintf(filename, sizeof(filename), "gfx/fonts/%s", font->name); font->shader = R_FindShader(filename, -1, qfalse); r_sequencenumber = save; if (!font->shader) { ri.Error(ERR_DROP, "Could not load font shader for %s\n", filename); } fontshader = (shader_t*)font->shader; if (fontshader->numUnfoggedPasses > 0) { for (i = 0; i < fontshader->numUnfoggedPasses; i++) { if (fontshader->stages[i] != NULL && fontshader->stages[i]->active) { fontshader->stages[i]->rgbGen = CGEN_ENTITY; fontshader->stages[i]->alphaGen = AGEN_ENTITY; } } font->trhandle = r_sequencenumber; } else { font->trhandle = r_sequencenumber; } } /* ================ R_FontTraceBegin / R_FontTraceEnd Temporary instrumentation, to settle whether the cost of the HUD is the strings themselves. Both string paths flush the render command buffer and then drive the back end directly, once per call - on a tiler with a framebuffer bound that can end the render pass and force a resolve every time. Counting the calls alongside the time spent in them says whether that is where the frame is going. ================ */ static int s_fontMsec; static int s_fontCalls; static int s_fontLastReport; static int R_FontTraceBegin( void ) { return ri.Milliseconds(); } static void R_FontTraceEnd( int start ) { int now = ri.Milliseconds(); s_fontMsec += now - start; s_fontCalls++; if ( now - s_fontLastReport < 1000 ) { return; } ri.Printf( PRINT_ALL, "VR font: %d calls, %dms a second\n", s_fontCalls, s_fontMsec ); s_fontLastReport = now; s_fontCalls = 0; s_fontMsec = 0; } void R_DrawString_sgl(fontheader_sgl_t* font, const char* text, float x, float y, int maxlen, const float *pvVirtualScreen) { float charHeight; float startx, starty; int i; float fWidthScale, fHeightScale; i = 0; startx = x; starty = y; if (pvVirtualScreen) { if (pvVirtualScreen[0]) { fWidthScale = pvVirtualScreen[0]; } else { fWidthScale = (double)glConfig.vidWidth / 640.0; } if (pvVirtualScreen[1]) { fHeightScale = pvVirtualScreen[1]; } else { fHeightScale = (double)glConfig.vidHeight / 480.0; } } if (!font) { return; } const int fontTraceStart = R_FontTraceBegin(); R_IssuePendingRenderCommands(); if (font->trhandle != r_sequencenumber) { font->shader = NULL; } if (!font->shader) { R_LoadFontShader(font); } Vector4Copy(backEnd.color2D, backEnd.entity2D.e.shaderRGBA); backEnd.currentEntity = &backEnd.entity2D; charHeight = s_fontHeightScale * font->height * s_fontGeneralScale; RB_BeginSurface((shader_t*)font->shader, 0, 0); for (i = 0; text[i]; i++) { unsigned char c; int indirected; letterloc_t* loc; c = text[i]; if (maxlen != -1 && i >= maxlen) { break; } switch (c) { case '\t': indirected = font->indirection[32]; if (indirected == -1) { ri.Printf(PRINT_DEVELOPER, "R_DrawString: no space-character in font!\n"); } else { x = s_fontGeneralScale * font->locations[indirected].size[0] * 256.0 * 3.0 + x; } break; case '\n': starty = charHeight + starty; x = startx; y = starty; break; case '\r': x = startx; break; default: indirected = font->indirection[c]; if (indirected == -1) { ri.Printf(PRINT_DEVELOPER, "R_DrawString: no 0x%02x-character in font!\n", c); indirected = font->indirection['?']; if (indirected == -1) { ri.Printf(PRINT_DEVELOPER, "R_DrawString: no '?' character in font!\n"); break; } // set the indirection for the next time font->indirection[c] = indirected; } RB_CHECKOVERFLOW(4, 6); loc = &font->locations[indirected]; // texture coordinates tess.texCoords[tess.numVertexes][0] = loc->pos[0]; tess.texCoords[tess.numVertexes][1] = loc->pos[1]; tess.texCoords[tess.numVertexes + 1][0] = loc->size[0] + loc->pos[0]; tess.texCoords[tess.numVertexes + 1][1] = loc->pos[1]; tess.texCoords[tess.numVertexes + 2][0] = loc->pos[0]; tess.texCoords[tess.numVertexes + 2][1] = loc->size[1] + loc->pos[1]; tess.texCoords[tess.numVertexes + 3][0] = loc->size[0] + loc->pos[0]; tess.texCoords[tess.numVertexes + 3][1] = loc->size[1] + loc->pos[1]; // vertices position tess.xyz[tess.numVertexes][0] = x; tess.xyz[tess.numVertexes][1] = y; tess.xyz[tess.numVertexes][2] = s_fontZ; tess.xyz[tess.numVertexes + 1][0] = x + s_fontGeneralScale * loc->size[0] * 256.0; tess.xyz[tess.numVertexes + 1][1] = y; tess.xyz[tess.numVertexes + 1][2] = s_fontZ; tess.xyz[tess.numVertexes + 2][0] = x; tess.xyz[tess.numVertexes + 2][1] = y + charHeight; tess.xyz[tess.numVertexes + 2][2] = s_fontZ; tess.xyz[tess.numVertexes + 3][0] = x + s_fontGeneralScale * loc->size[0] * 256.0; tess.xyz[tess.numVertexes + 3][1] = y + charHeight; tess.xyz[tess.numVertexes + 3][2] = s_fontZ; // indices tess.indexes[tess.numIndexes] = tess.numVertexes; tess.indexes[tess.numIndexes + 1] = tess.numVertexes + 1; tess.indexes[tess.numIndexes + 2] = tess.numVertexes + 2; tess.indexes[tess.numIndexes + 3] = tess.numVertexes + 1; tess.indexes[tess.numIndexes + 4] = tess.numVertexes + 3; tess.indexes[tess.numIndexes + 5] = tess.numVertexes + 2; if (pvVirtualScreen) { // scale the string properly if virtual screen tess.xyz[tess.numVertexes][0] *= fWidthScale; tess.xyz[tess.numVertexes][1] *= fHeightScale; tess.xyz[tess.numVertexes + 1][0] *= fWidthScale; tess.xyz[tess.numVertexes + 1][1] *= fHeightScale; tess.xyz[tess.numVertexes + 2][0] *= fWidthScale; tess.xyz[tess.numVertexes + 2][1] *= fHeightScale; tess.xyz[tess.numVertexes + 3][0] *= fWidthScale; tess.xyz[tess.numVertexes + 3][1] *= fHeightScale; } x += s_fontGeneralScale * loc->size[0] * 256.0; tess.numVertexes += 4; tess.numIndexes += 6; break; } } RB_EndSurface(); R_FontTraceEnd( fontTraceStart ); } void R_DrawString(fontheader_t* font, const char* text, float x, float y, int maxlen, const float *pvVirtualScreen) { int i; int code; unsigned short uch; char buffer[512]; size_t buflen; int cursgl; float curX, curY; float curHeight; if (!font->numPages) { if (font->sgl[0]) { R_DrawString_sgl(font->sgl[0], text, x, y, maxlen, pvVirtualScreen); } return; } if (maxlen < 0) { maxlen = strlen(text); } curX = x; curY = y; curHeight = 0.f; cursgl = -1; buflen = 0; i = 0; while(i < maxlen) { fontchartable_t* ct; uch = text[i]; i++; if (DBCSIsLeadByte(font, uch)) { uch = (uch << 8) | text[i]; i++; } if (!uch) { break; } if (uch == '\n' || uch == '\r') { buffer[buflen] = 0; R_DrawString_sgl(font->sgl[cursgl], buffer, curX, curY, maxlen, pvVirtualScreen); curX = x; curHeight = 0.f; buflen = 0; if (uch == '\n') { curY += font->sgl[0]->height * s_fontGeneralScale * s_fontHeightScale; } continue; } code = CodeSearch(font, uch); if (code < 0) { continue; } if (cursgl == -1) { cursgl = font->charTable[code].index; } ct = &font->charTable[code]; if (cursgl != ct->index || buflen >= ARRAY_LEN(buffer) - 2) { buffer[buflen] = 0; R_DrawString_sgl(font->sgl[cursgl], buffer, curX, curY, maxlen, pvVirtualScreen); curX += curHeight; curHeight = 0.f; buflen = 0; ct = &font->charTable[code]; cursgl = ct->index; } buffer[buflen++] = ct->loc; curHeight += font->sgl[cursgl]->locations[ct->loc].size[0] * 256.0f; } if (buflen) { buffer[buflen] = 0; R_DrawString_sgl(font->sgl[cursgl], buffer, curX, y, buflen, pvVirtualScreen); } } void R_DrawFloatingString_sgl(fontheader_sgl_t* font, const char* text, const vec3_t org, const vec4_t color, float scale, int maxlen) { shader_t* fontshader; qhandle_t fsh; float charWidth, charHeight; int i; vec3_t pos; polyVert_t verts[4]; if (!font) { return; } R_IssuePendingRenderCommands(); if (font->trhandle != r_sequencenumber) { font->shader = NULL; } if (!font->shader) { R_LoadFontShader(font); } i = 0; fontshader = (shader_t*)font->shader; fsh = 0; for (i = 0; i < tr.numShaders; i++) { if (fontshader == tr.shaders[i]) { fsh = i; break; } } i = 0; charHeight = font->height * s_fontHeightScale * s_fontGeneralScale * scale; VectorCopy(org, pos); for (i = 0; text[i]; i++) { unsigned char c; int indirected; letterloc_t* loc; c = text[i]; indirected = font->indirection[c]; if (indirected == -1) { ri.Printf(PRINT_ALL, "R_DrawFloatingString: no 0x%02x-character in font!\n", c); continue; } loc = &font->locations[indirected]; // vertices color verts[0].modulate[0] = (int)(color[0] * 255.0); verts[0].modulate[1] = (int)(color[1] * 255.0); verts[0].modulate[2] = (int)(color[2] * 255.0); verts[0].modulate[3] = (int)(color[3] * 255.0); verts[1].modulate[0] = verts[0].modulate[0]; verts[1].modulate[1] = verts[0].modulate[1]; verts[1].modulate[2] = verts[0].modulate[2]; verts[1].modulate[3] = verts[0].modulate[3]; verts[2].modulate[0] = verts[0].modulate[0]; verts[2].modulate[1] = verts[0].modulate[1]; verts[2].modulate[2] = verts[0].modulate[2]; verts[2].modulate[3] = verts[0].modulate[3]; verts[3].modulate[0] = verts[0].modulate[0]; verts[3].modulate[1] = verts[0].modulate[1]; verts[3].modulate[2] = verts[0].modulate[2]; verts[3].modulate[3] = verts[0].modulate[3]; // texture coordinates verts[0].st[0] = loc->pos[0]; verts[0].st[1] = loc->pos[1]; verts[1].st[0] = loc->pos[0] + font->locations[indirected].size[0]; verts[1].st[1] = loc->pos[1]; verts[2].st[0] = verts[1].st[0]; verts[2].st[1] = loc->pos[1] + loc->size[1]; verts[3].st[0] = loc->pos[0]; verts[3].st[1] = verts[2].st[1]; VectorCopy(pos, verts[3].xyz); charWidth = font->locations[indirected].size[0] * 256.0 * s_fontGeneralScale * scale; verts[2].xyz[0] = pos[0] + tr.refdef.viewaxis[1][0] * -charWidth; verts[2].xyz[1] = pos[1] + tr.refdef.viewaxis[1][1] * -charWidth; verts[2].xyz[2] = pos[2] + tr.refdef.viewaxis[1][2] * -charWidth; verts[1].xyz[0] = verts[2].xyz[0] + charHeight * tr.refdef.viewaxis[2][0]; verts[1].xyz[1] = verts[2].xyz[1] + charHeight * tr.refdef.viewaxis[2][1]; verts[1].xyz[2] = verts[2].xyz[2] + charHeight * tr.refdef.viewaxis[2][2]; verts[0].xyz[1] = verts[1].xyz[1] + tr.refdef.viewaxis[1][1] * charWidth; verts[0].xyz[2] = verts[1].xyz[2] + tr.refdef.viewaxis[1][2] * charWidth; verts[0].xyz[0] = verts[1].xyz[0] + tr.refdef.viewaxis[1][0] * charWidth; if (RE_AddPolyToScene2(fsh, 4, verts, 0)) { ++tr.refdef.numPolys; } pos[0] = verts[2].xyz[0]; pos[1] = verts[2].xyz[1]; pos[2] = verts[2].xyz[2]; } } void R_DrawFloatingString(fontheader_t* font, const char* text, const vec3_t org, const vec4_t color, float scale, int maxlen) { return R_DrawFloatingString_sgl(font->sgl[0], text, org, color, scale, maxlen); } float R_GetFontHeight(const fontheader_t* font) { if (!font || !font->sgl[0]) { return 0.0; } return font->sgl[0]->height * s_fontGeneralScale * s_fontHeightScale; } float R_GetFontStringWidth_sgl(const fontheader_sgl_t* font, const char* s) { float widths; int i; widths = 0.0; if (!font) { return 0.0; } for (i = 0; s[i]; i++) { int indirected; char c = *s; if (c == '\t') { indirected = font->indirection[32]; if (indirected != -1) { widths += font->locations[indirected].size[0] * 3.0; } else { ri.Printf(PRINT_ALL, "R_GetFontStringWidth: no space-character in font!\n"); } } else { indirected = font->indirection[c]; if (indirected != -1) { widths += font->locations[indirected].size[0]; } else { ri.Printf(PRINT_ALL, "R_GetFontStringWidth: no 0x%02x-character in font!\n", c); } } } return widths * s_fontGeneralScale * 256.0; } float R_GetFontStringWidth(const fontheader_t* font, const char* s) { int i; int code; fontchartable_t* ct; float width = 0.f; if (!font->numPages) { return R_GetFontStringWidth_sgl(font->sgl[0], s); } i = 0; while(s[i]) { unsigned char uch = s[i++]; if (DBCSIsLeadByte(font, uch)) { uch = (uch << 8) | s[i++]; if (!uch) { break; } } if (uch == '\t') { code = CodeSearch(font, ' '); if (code >= 0) { ct = &font->charTable[code]; width += font->sgl[ct->index]->locations[ct->loc].size[0] * 3.f; } } else { code = CodeSearch(font, uch); if (code >= 0) { ct = &font->charTable[code]; width += font->sgl[ct->index]->locations[ct->loc].size[0]; } } } return width; }