#include "quakedef.h" #ifndef SERVERONLY #include "shader.h" #include "gl_draw.h" #ifdef _WIN32 #ifdef _XBOX #include #else #include #endif #endif #include void Font_Init(void); void Font_Shutdown(void); struct font_s *Font_LoadFont(const char *fontfilename, float height); void Font_Free(struct font_s *f); void Font_BeginString(struct font_s *font, float vx, float vy, int *px, int *py); void Font_BeginScaledString(struct font_s *font, float vx, float vy, float szx, float szy, float *px, float *py); /*avoid using*/ void Font_Transform(float vx, float vy, int *px, int *py); int Font_CharHeight(void); float Font_CharScaleHeight(void); //FIXME: if we want to do emoji properly, then we're going to need support for variants somehow // easiest is probably to make the next codepoint available too (and consumable) // handling variants in the font cache is another issue. gah. not worth it. int Font_CharWidth(unsigned int charflags, unsigned int codepoint); float Font_CharScaleWidth(unsigned int charflags, unsigned int codepoint); int Font_CharEndCoord(struct font_s *font, int x, unsigned int charflags, unsigned int codepoint); int Font_DrawChar(int px, int py, unsigned int charflags, unsigned int codepoint); float Font_DrawScaleChar(float px, float py, unsigned int charflags, unsigned int codepoint); /*avoid using*/ void Font_EndString(struct font_s *font); int Font_LineBreaks(conchar_t *start, conchar_t *end, int maxpixelwidth, int maxlines, conchar_t **starts, conchar_t **ends); struct font_s *font_default; struct font_s *font_console; struct font_s *font_tiny; static int font_be_flags; extern unsigned int r2d_be_flags; #ifdef AVAIL_FREETYPE #include #include FT_FREETYPE_H #ifndef FT_LOAD_COLOR #define FT_LOAD_COLOR (1L<<20) #endif static FT_Library fontlib; #ifdef FREETYPE_STATIC #define pFT_Init_FreeType FT_Init_FreeType #define pFT_Load_Char FT_Load_Char #define pFT_Get_Char_Index FT_Get_Char_Index #define pFT_Set_Pixel_Sizes FT_Set_Pixel_Sizes #define pFT_Select_Size FT_Select_Size #define pFT_New_Face FT_New_Face #define pFT_New_Memory_Face FT_New_Memory_Face #define pFT_Done_Face FT_Done_Face #else qboolean triedtoloadfreetype; dllhandle_t *fontmodule; FT_Error (VARGS *pFT_Init_FreeType) (FT_Library *alibrary); FT_Error (VARGS *pFT_Load_Char) (FT_Face face, FT_ULong char_code, FT_Int32 load_flags); FT_UInt (VARGS *pFT_Get_Char_Index) (FT_Face face, FT_ULong charcode); FT_Error (VARGS *pFT_Set_Pixel_Sizes) (FT_Face face, FT_UInt pixel_width, FT_UInt pixel_height); FT_Error (VARGS *pFT_Select_Size) (FT_Face face, FT_Int strike_index); FT_Error (VARGS *pFT_New_Face) (FT_Library library, const char *pathname, FT_Long face_index, FT_Face *aface); FT_Error (VARGS *pFT_New_Memory_Face) (FT_Library library, const FT_Byte* file_base, FT_Long file_size, FT_Long face_index, FT_Face *aface); FT_Error (VARGS *pFT_Done_Face) (FT_Face face); #endif #else typedef unsigned int FT_Pixel_Mode; //for consistency even without freetype support. #endif #ifndef FT_PIXEL_MODE_GRAY #define FT_PIXEL_MODE_GRAY 2 #endif #ifndef FT_PIXEL_MODE_BGRA #define FT_PIXEL_MODE_BGRA 7 //added in FT 2.5 #endif #define FT_PIXEL_MODE_RGBA_SA (100) //RGBA, straight alpha. not in freetype. #define FT_PIXEL_MODE_RGBA (101) //RGBA, premultiplied alpha. not in freetype. #ifdef QUAKEHUD static const char *imgs[] = { //0xe10X "e100","e101", "inv_shotgun", "inv_sshotgun", "inv_nailgun", "inv_snailgun", "inv_rlaunch", "inv_srlaunch", "inv_lightng", //8 "e109","e10a","e10b","e10c","e10d","e10e","e10f", //0xe11X "e110","e111", "inv2_shotgun", "inv2_sshotgun", "inv2_nailgun", "inv2_snailgun", "inv2_rlaunch", "inv2_srlaunch", "inv2_lightng", "e119","e11a","e11b","e11c","e11d","e11e","e11f", //0xe12X "sb_shells", "sb_nails", "sb_rocket", "sb_cells", "sb_armor1", "sb_armor2", "sb_armor3", "e127","e128","e129","e12a","e12b","e12c","e12d","e12e","e12f", //0xe13X "sb_key1", "sb_key2", "sb_invis", "sb_invuln", "sb_suit", "sb_quad", "sb_sigil1", "sb_sigil2", "sb_sigil3", "sb_sigil4", "e13a","e13b","e13c","e13d","e13e","e13f", //0xe14X "face1", "face_p1", "face2", "face_p2", "face3", "face_p3", "face4", "face_p4", "face5", "face_p5", "face_invis", "face_invul2", "face_inv2", "face_quad", "e14e", "e14f", //0xe15X "e150", "e151", "e152", "e153", "e154", "e155", "e156", "e157", "e158", "e159", "e15a", "e15b", "e15c", "e15d", "e15e", "e15f", //0xe16X "e160", "e161", "e162", "e163", "e164", "e165", "e166", "e167", "e168", "e169", "e16a", "e16b", "e16c", "e16d", "e16e", "e16f" }; #endif #define FONT_MAXCHARS 0x110000 //as defined by UTF-16, and thus applied to all unicode because UTF-16 is the crappy limited one. #define FONTBLOCKS ((FONT_MAXCHARS+FONTBLOCKSIZE-1)/FONTBLOCKSIZE) #define FONTBLOCKSIZE 0x100 //must be power-of-two #define FONTBLOCKMASK (FONTBLOCKSIZE-1) #define FONTIMAGES (1<<2) //this is total, not per font. #define FIMAGEIDXTYPE unsigned char #define CHARIDXTYPE unsigned int #define INVALIDPLANE ((1<<(8*sizeof(FIMAGEIDXTYPE)))-1) #define BITMAPPLANE ((1<<(8*sizeof(FIMAGEIDXTYPE)))-2) #define DEFAULTPLANE ((1<<(8*sizeof(FIMAGEIDXTYPE)))-3) #define SINGLEPLANE ((1<<(8*sizeof(FIMAGEIDXTYPE)))-4) #define TRACKERIMAGE ((1<<(8*sizeof(FIMAGEIDXTYPE)))-5) #define FIMAGEWIDTH (1<<10) #define FIMAGEHEIGHT FIMAGEWIDTH #define FONTPLANES FONTIMAGES #define PLANEWIDTH FIMAGEWIDTH #define PLANEHEIGHT FIMAGEHEIGHT //windows' font linking allows linking multiple extra fonts to a main font. //this means that a single selected font can use chars from lots of different files if the first one(s) didn't provide that font. //they're provided as fallbacks. #define MAX_FACES 32 typedef struct fontface_s { struct fontface_s *fnext; struct fontface_s **flink; //like prev, but not. char name[MAX_OSPATH]; int refs; struct { qbyte *data; size_t width; size_t height; } horiz; #ifdef HALFLIFEMODELS struct { int fontheight1; int imgheight; int rows; int fontheight2; struct { qbyte x; qbyte y; qbyte width; qbyte pad; } chartab[256]; // int unk[4]; qbyte data[1];//[256*imgheight]; //int pad //palette[256*3]; } *halflife; #endif #ifdef AVAIL_FREETYPE struct { int activeheight; //needs reconfiguring when different sizes are used int actualsize; //sometimes that activeheight isn't usable. :( FT_Face face; void *membuf; } ft; #endif } fontface_t; static fontface_t *faces; #define GEN_CONCHAR_GLYPHS 0 //set to 0 or 1 to define whether to generate glyphs from conchars too, or if it should just draw them as glquake always used to extern cvar_t cl_noblink; extern cvar_t con_ocranaleds; typedef struct font_s { //FIXME: use a hash table? will need it if we go beyond ucs-2. //currently they're in a single block so the font can be checked from scanning the active chars when shutting down. we could instead scan all 65k chars in every font instead... struct charcache_s { struct charcache_s *nextchar; FIMAGEIDXTYPE texplane; unsigned char advance; //how wide this char is, when drawn unsigned short block; //to quickly find the char again //texture offsets. unsigned short bmx; unsigned short bmy; //texture/screen pixel sizes. unsigned char bmw; unsigned char bmh; unsigned short flags; #define CHARF_FORCEWHITE (1u<<0) //coloured emoji should not be tinted. //screen offsets. short top; short left; } *chars[FONTBLOCKS]; char name[MAX_OSPATH]; texid_t singletexture; unsigned short charheight; unsigned short faces; fontface_t *face[MAX_FACES]; struct font_s *alt; vec3_t tint; vec3_t alttint; } font_t; union byte_vec4_u { byte_vec4_t rgba; quint32_t c; }; //shared between fonts. typedef struct { texid_t texnum[FONTIMAGES]; texid_t defaultfont; texid_t trackerimage; union byte_vec4_u plane[PLANEWIDTH*PLANEHEIGHT]; //tracks the current plane FIMAGEIDXTYPE activeplane; unsigned short planerowx; unsigned short planerowy; unsigned short planerowh; qboolean planechanged; struct charcache_s *oldestchar; struct charcache_s *newestchar; shader_t *shader; shader_t *backshader; } fontplanes_t; static fontplanes_t fontplanes; #define FONT_CHAR_BUFFER 512 static index_t font_indicies[FONT_CHAR_BUFFER*6]; static vecV_t font_coord[FONT_CHAR_BUFFER*4]; static vecV_t font_backcoord[FONT_CHAR_BUFFER*4]; static vec2_t font_texcoord[FONT_CHAR_BUFFER*4]; static union byte_vec4_u font_forecoloura[FONT_CHAR_BUFFER*4]; static union byte_vec4_u font_backcoloura[FONT_CHAR_BUFFER*4]; static mesh_t font_foremesh; static mesh_t font_backmesh; static texid_t font_texture; static int font_colourmask; static union byte_vec4_u font_forecolour; static union byte_vec4_u font_backcolour; static avec4_t font_foretint; static struct font_s *curfont; static float curfont_scale[2]; static qboolean curfont_scaled; extern cvar_t r_font_linear; //^Ue2XX static struct { image_t *image; char name[64]; } trackerimages[256]; static int numtrackerimages; #define TRACKERFIRST 0xe200 int Font_RegisterTrackerImage(const char *image) { int i; for (i = 0; i < numtrackerimages; i++) { if (!strcmp(trackerimages[i].name, image)) return TRACKERFIRST + i; } if (numtrackerimages == 256) return 0; trackerimages[i].image = NULL; //actually load it elsewhere, because we're lazy. Q_strncpyz(trackerimages[i].name, image, sizeof(trackerimages[i].name)); numtrackerimages++; return TRACKERFIRST + i; } //called from the font display code for tracker images static image_t *Font_GetTrackerImage(unsigned int imid) { if (!trackerimages[imid].image) { if (!*trackerimages[imid].name) return NULL; trackerimages[imid].image = Image_GetTexture(trackerimages[imid].name, NULL, IF_PREMULTIPLYALPHA|IF_UIPIC, NULL, NULL, 0, 0, TF_INVALID); } if (!trackerimages[imid].image) return NULL; if (trackerimages[imid].image->status != TEX_LOADED) return NULL; return trackerimages[imid].image; } qboolean Font_TrackerValid(unsigned int imid) { imid -= TRACKERFIRST; if (imid >= countof(trackerimages)) return false; if (!trackerimages[imid].image) { if (!*trackerimages[imid].name) return false; trackerimages[imid].image = Image_GetTexture(trackerimages[imid].name, NULL, IF_PREMULTIPLYALPHA|IF_UIPIC, NULL, NULL, 0, 0, TF_INVALID); } if (!trackerimages[imid].image) return false; if (trackerimages[imid].image->status == TEX_LOADING) COM_WorkerPartialSync(trackerimages[imid].image, &trackerimages[imid].image->status, TEX_LOADING); if (trackerimages[imid].image->status != TEX_LOADED) return false; return true; } //called at load time - initalises font buffers void Font_Init(void) { int i; TEXASSIGN(fontplanes.defaultfont, r_nulltex); //clear tracker images, just in case they were still set for the previous renderer context for (i = 0; i < sizeof(trackerimages)/sizeof(trackerimages[0]); i++) trackerimages[i].image = NULL; font_foremesh.indexes = font_indicies; font_foremesh.xyz_array = font_coord; font_foremesh.st_array = font_texcoord; font_foremesh.colors4b_array = &font_forecoloura->rgba; font_backmesh.indexes = font_indicies; font_backmesh.xyz_array = font_backcoord; font_backmesh.st_array = font_texcoord; font_backmesh.colors4b_array = &font_backcoloura->rgba; for (i = 0; i < FONT_CHAR_BUFFER; i++) { font_indicies[i*6+0] = i*4+0; font_indicies[i*6+1] = i*4+1; font_indicies[i*6+2] = i*4+2; font_indicies[i*6+3] = i*4+0; font_indicies[i*6+4] = i*4+2; font_indicies[i*6+5] = i*4+3; } for (i = 0; i < FONTPLANES; i++) { TEXASSIGN(fontplanes.texnum[i], Image_CreateTexture("***fontplane***", NULL, IF_UIPIC|(r_font_linear.ival?IF_LINEAR:IF_NEAREST)|IF_NOPICMIP|IF_NOMIPMAP|IF_NOGAMMA)); } fontplanes.shader = R_RegisterShader("ftefont", SUF_2D, "{\n" "if $nofixed\n" "program default2d\n" "endif\n" "nomipmaps\n" "{\n" "if r_font_linear\n" "map $linear:$diffuse\n" "else\n" "map $nearest:$diffuse\n" "endif\n" "rgbgen vertex\n" "alphagen vertex\n" "blendfunc gl_one gl_one_minus_src_alpha\n" "}\n" "}\n" ); fontplanes.backshader = R_RegisterShader("ftefontback", SUF_2D, "{\n" "nomipmaps\n" "{\n" "map $whiteimage\n" "rgbgen vertex\n" "alphagen vertex\n" "blendfunc gl_one gl_one_minus_src_alpha\n" "}\n" "}\n" ); font_colourmask = ~0; } //flush the font buffer, by drawing it to the screen static void Font_Flush(void) { R2D_Flush = NULL; if (!font_foremesh.numindexes) return; if (fontplanes.planechanged) { Image_Upload(fontplanes.texnum[fontplanes.activeplane], TF_RGBA32, (void*)fontplanes.plane, NULL, PLANEWIDTH, PLANEHEIGHT, IF_UIPIC|IF_NEAREST|IF_NOPICMIP|IF_NOMIPMAP|IF_NOGAMMA); fontplanes.planechanged = false; } font_foremesh.istrifan = (font_foremesh.numvertexes == 4); if ((font_colourmask & (CON_RICHFORECOLOUR|CON_NONCLEARBG)) == CON_NONCLEARBG && font_foremesh.numindexes) { font_backmesh.numindexes = font_foremesh.numindexes; font_backmesh.numvertexes = font_foremesh.numvertexes; font_backmesh.istrifan = font_foremesh.istrifan; BE_DrawMesh_Single(fontplanes.backshader, &font_backmesh, NULL, font_be_flags); } TEXASSIGN(fontplanes.shader->defaulttextures->base, font_texture); BE_DrawMesh_Single(fontplanes.shader, &font_foremesh, NULL, font_be_flags); font_foremesh.numindexes = 0; font_foremesh.numvertexes = 0; } static int Font_BeginChar(texid_t tex) { int fvert; if (font_foremesh.numindexes >= FONT_CHAR_BUFFER*6 || font_texture != tex) { Font_Flush(); TEXASSIGNF(font_texture, tex); } fvert = font_foremesh.numvertexes; font_foremesh.numindexes += 6; font_foremesh.numvertexes += 4; return fvert; } //clear the shared planes and free memory etc void Font_Shutdown(void) { int i; for (i = 0; i < FONTPLANES; i++) TEXASSIGN(fontplanes.texnum[i], r_nulltex); fontplanes.activeplane = 0; fontplanes.oldestchar = NULL; fontplanes.newestchar = NULL; fontplanes.planechanged = 0; fontplanes.planerowx = 0; fontplanes.planerowy = 0; fontplanes.planerowh = 0; } //we got too many chars and switched to a new plane - purge the chars in that plane void Font_FlushPlane(void) { /* assumption: oldest chars must be of the oldest plane */ //we've not broken anything yet, flush while we can Font_Flush(); if (fontplanes.planechanged) { Image_Upload(fontplanes.texnum[fontplanes.activeplane], TF_RGBA32, (void*)fontplanes.plane, NULL, PLANEWIDTH, PLANEHEIGHT, IF_UIPIC|IF_NEAREST|IF_NOPICMIP|IF_NOMIPMAP|IF_NOGAMMA); fontplanes.planechanged = false; } fontplanes.activeplane++; fontplanes.activeplane = fontplanes.activeplane % FONTPLANES; fontplanes.planerowh = 0; fontplanes.planerowx = 0; fontplanes.planerowy = 0; while (fontplanes.oldestchar) { if (fontplanes.oldestchar->texplane != fontplanes.activeplane) break; //remove it from the list of active chars, and invalidate it fontplanes.oldestchar->texplane = INVALIDPLANE; fontplanes.oldestchar = fontplanes.oldestchar->nextchar; } if (!fontplanes.oldestchar) fontplanes.newestchar = NULL; } static struct charcache_s *Font_GetCharIfLoaded(font_t *f, unsigned int charidx) { struct charcache_s *c = f->chars[charidx/FONTBLOCKSIZE]; if (c) { c += charidx&FONTBLOCKMASK; if (c->texplane == INVALIDPLANE) c = NULL; } return c; } static struct charcache_s *Font_GetCharStore(font_t *f, unsigned int charidx) { //should only be called if generating a char cache struct charcache_s *c; size_t i; c = f->chars[charidx/FONTBLOCKSIZE]; if (!c) { c = Z_Malloc(sizeof(struct charcache_s) * FONTBLOCKSIZE); f->chars[charidx/FONTBLOCKSIZE] = c; for (i = 0; i < FONTBLOCKSIZE; i++) { c[i].texplane = INVALIDPLANE; } } c += charidx&FONTBLOCKMASK; c->block = charidx/FONTBLOCKSIZE; return c; } static struct charcache_s *Font_CopyChar(font_t *f, unsigned int oldcharidx, unsigned int newcharidx) { struct charcache_s *new, *old = Font_GetCharIfLoaded(f, oldcharidx); if (!old) return NULL; new = Font_GetCharStore(f, newcharidx); *new = *old; new->block = newcharidx/FONTBLOCKSIZE; return new; } //loads a new image into a given character slot for the given font. //note: make sure it doesn't already exist or things will get cyclic //alphaonly says if its a greyscale image. false means rgba. static struct charcache_s *Font_LoadGlyphData(font_t *f, CHARIDXTYPE charidx, FT_Pixel_Mode pixelmode, void *data, unsigned int bmw, unsigned int bmh, unsigned int pitch) { int x, y; union byte_vec4_u *out; struct charcache_s *c = Font_GetCharStore(f, charidx); int pad = 0; #define BORDERCOLOUR 0 if (fontplanes.texnum[0]->flags & IF_LINEAR) pad += 1; //pad the image data to avoid sampling outside if (fontplanes.planerowx + (int)bmw+pad*2 >= PLANEWIDTH) { fontplanes.planerowx = 0; fontplanes.planerowy += fontplanes.planerowh; fontplanes.planerowh = 0; } if (fontplanes.planerowy+(int)bmh+pad*2 >= PLANEHEIGHT) Font_FlushPlane(); if (fontplanes.newestchar) fontplanes.newestchar->nextchar = c; else fontplanes.oldestchar = c; fontplanes.newestchar = c; c->nextchar = NULL; c->flags = 0; c->texplane = fontplanes.activeplane; c->bmx = fontplanes.planerowx+pad; c->bmy = fontplanes.planerowy+pad; c->bmw = bmw; c->bmh = bmh; if (fontplanes.planerowh < (int)bmh+pad*2) fontplanes.planerowh = bmh+pad*2; fontplanes.planerowx += bmw+pad*2; out = &fontplanes.plane[c->bmx+((int)c->bmy-pad)*PLANEHEIGHT]; if (pixelmode == FT_PIXEL_MODE_GRAY) { //8bit font for (y = -pad; y < 0; y++) { for (x = -pad; x < (int)bmw+pad; x++) out[x].c = BORDERCOLOUR; out += PLANEWIDTH; } for (; y < bmh; y++) { for (x = -pad; x < 0; x++) out[x].c = BORDERCOLOUR; for (; x < bmw; x++) out[x].c = 0x01010101 * ((unsigned char*)data)[x]; for (; x < bmw+pad; x++) out[x].c = BORDERCOLOUR; data = (char*)data + pitch; out += PLANEWIDTH; } for (; y < bmh+pad; y++) { for (x = -pad; x < (int)bmw+pad; x++) out[x].c = BORDERCOLOUR; out += PLANEWIDTH; } } else if ((unsigned int)pixelmode == FT_PIXEL_MODE_RGBA_SA) { //rgba source using standard alpha. //(we'll multiply out the alpha for the gpu) for (y = -pad; y < 0; y++) { for (x = -pad; x < (int)bmw+pad; x++) out[x].c = BORDERCOLOUR; out += PLANEWIDTH; } for (; y < bmh; y++) { for (x = -pad; x < 0; x++) out[x].c = BORDERCOLOUR; for (; x < bmw; x++) { if (((unsigned char*)data)[x*4+3] == 255) out[x] = ((union byte_vec4_u*)data)[x]; else { out[x].rgba[0] = (((unsigned char*)data)[x*4+3]*((unsigned char*)data)[x*4+0])<<8; out[x].rgba[1] = (((unsigned char*)data)[x*4+3]*((unsigned char*)data)[x*4+1])<<8; out[x].rgba[2] = (((unsigned char*)data)[x*4+3]*((unsigned char*)data)[x*4+2])<<8; out[x].rgba[3] = ((unsigned char*)data)[x*4+3]; } } for (; x < bmw+pad; x++) out[x].c = BORDERCOLOUR; data = (char*)data + pitch; out += PLANEWIDTH; } for (; y < (int)bmh+pad; y++) { for (x = -pad; x < (int)bmw+pad; x++) out[x].c = BORDERCOLOUR; out += PLANEWIDTH; } } else if (pixelmode == FT_PIXEL_MODE_BGRA) { //bgra srgb font, already premultiplied for (y = -pad; y < 0; y++) { for (x = -pad; x < (int)bmw+pad; x++) out[x].c = BORDERCOLOUR; out += PLANEWIDTH; } for (; y < bmh; y++) { for (x = -pad; x < 0; x++) out[x].c = BORDERCOLOUR; for (; x < bmw; x++) { out[x].rgba[0] = ((unsigned char*)data)[x*4+2]; out[x].rgba[1] = ((unsigned char*)data)[x*4+1]; out[x].rgba[2] = ((unsigned char*)data)[x*4+0]; out[x].rgba[3] = ((unsigned char*)data)[x*4+3]; } for (; x < bmw+pad; x++) out[x].c = BORDERCOLOUR; data = (char*)data + pitch; out += PLANEWIDTH; } for (; y < bmh+pad; y++) { for (x = -pad; x < (int)bmw+pad; x++) out[x].c = BORDERCOLOUR; out += PLANEWIDTH; } c->flags = CHARF_FORCEWHITE; //private glyph colours } else if ((unsigned int)pixelmode == FT_PIXEL_MODE_RGBA) { //bgra srgb font, already premultiplied for (y = -pad; y < 0; y++) { for (x = -pad; x < (int)bmw+pad; x++) out[x].c = BORDERCOLOUR; out += PLANEWIDTH; } for (; y < bmh; y++) { for (x = -pad; x < 0; x++) out[x].c = BORDERCOLOUR; for (; x < bmw; x++) out[x].c = ((unsigned int*)data)[x]; for (; x < bmw+pad; x++) out[x].c = BORDERCOLOUR; data = (char*)data + pitch; out += PLANEWIDTH; } for (; y < bmh+pad; y++) { for (x = -pad; x < (int)bmw+pad; x++) out[x].c = BORDERCOLOUR; out += PLANEWIDTH; } c->flags = CHARF_FORCEWHITE; //private glyph colours } fontplanes.planechanged = true; return c; } unsigned short hex[16] = { /*0*/ (7<<0)|(5<<3)|(5<<6)|(5<<9)|(7<<12), /*1*/ (2<<0)|(2<<3)|(2<<6)|(2<<9)|(2<<12), /*2*/ (7<<0)|(1<<3)|(7<<6)|(4<<9)|(7<<12), /*3*/ (7<<0)|(1<<3)|(3<<6)|(1<<9)|(7<<12), /*4*/ (5<<0)|(5<<3)|(7<<6)|(1<<9)|(1<<12), /*5*/ (7<<0)|(4<<3)|(7<<6)|(1<<9)|(7<<12), /*6*/ (4<<0)|(4<<3)|(7<<6)|(5<<9)|(7<<12), /*7*/ (7<<0)|(1<<3)|(1<<6)|(1<<9)|(1<<12), /*8*/ (7<<0)|(5<<3)|(7<<6)|(5<<9)|(7<<12), /*9*/ (7<<0)|(5<<3)|(7<<6)|(1<<9)|(1<<12), /*A*/ (7<<0)|(5<<3)|(7<<6)|(5<<9)|(5<<12), /*B*/ (6<<0)|(5<<3)|(7<<6)|(5<<9)|(6<<12), /*C*/ (7<<0)|(5<<3)|(4<<6)|(5<<9)|(7<<12), /*D*/ (6<<0)|(5<<3)|(5<<6)|(5<<9)|(6<<12), /*E*/ (7<<0)|(4<<3)|(6<<6)|(4<<9)|(7<<12), /*F*/ (7<<0)|(4<<3)|(6<<6)|(4<<9)|(4<<12) }; static struct charcache_s *Font_LoadPlaceholderGlyph(font_t *f, CHARIDXTYPE charidx) { struct charcache_s *c; unsigned int out[169*4], i, o, g, b, w, h, d, n; if (charidx == 0xe080 || charidx == 0xe081 || charidx == 0xe082 || charidx == 0xe083) { //scrollbar glyps w = min(16,f->charheight); h = max(1,w/8); d = 0; if (charidx == 0xe083) //centre { h *= 3; memset(out+w*h*0, 0xff, w*4*h); } else { memset(out+w*h*0, 0xff, w*4*h); memset(out+w*h*1, 0x00, w*4*h); memset(out+w*h*2, 0xff, w*4*h); for (i = 0; i < h; i++) { if (charidx == 0xe080) //left memset(out+w*(i+h*1), 0xff, h*4); if (charidx == 0xe082) //right memset(out+w*(i+h*1)+w-h, 0xff, h*4); } h *= 3; } } else if (charidx == 0xe01d || charidx == 0xe01e || charidx == 0xe01f) { //horizontal separator w = min(16,f->charheight); h = max(1,w/8); d = 0; memset(out, 0xff, w*4*h); } else if (charidx == 0xe00b) { //console input cursor h = min(16,f->charheight); w = max(1,h/8); d = 1; memset(out, 0xff, w*4*h); } else if (charidx == 0xe00d) { //filled > arrow indicator (used by the menus) h = min(16,f->charheight); w = (h+1)/2; d = 1; memset(out, 0xff, w*4*h); for (i = 0; i < w; i++) { memset(out + w*i + (i+1), 0x0, (w-i-1)*4); memset(out + w*(h-i-1) + (i+1), 0x0, (w-i-1)*4); } } else { d = (f->charheight >= 11); if (d) { //two rows h = 11; if (charidx > 0xffff) n = 3; else if (charidx > 0xff) n = 2; else n = 1; w = n*4-1; n*=2; } else { //single row. bye bye fixed-width. if (charidx > 0xfffff) n = 6; else if (charidx > 0xffff) n = 5; else if (charidx > 0xfff) n = 4; else if (charidx > 0xff) n = 3; else n = 2; w = n*4-1; h = 5; } //figure out if we can get away with giving it a little more border to boost readability b = (h+2 < f->charheight); w += b*2; h += b*2; memset(out, 0xff, sizeof(out)); for (i = 0; i < n; i++) { g = hex[0xf & (charidx>>(i<<2))]; o = b + w*b; if (d) { //stradle them over the two rows. if (i >= (n>>1)) o += 4*(n-i-1); else { o += 4*((n>>1)-i-1); o += w * 6; } } else o += 4*(n-i-1); //just arrange them in order for (; g; g>>=3, o+=w) { if (g & 4) out[o+0] = 0xff0000ff; if (g & 2) out[o+1] = 0xff0000ff; if (g & 1) out[o+2] = 0xff0000ff; } } d = 1; } c = Font_LoadGlyphData(f, charidx, FT_PIXEL_MODE_RGBA, out, w, h, w*4); if (c) { c->advance = w+d; c->left = 0; c->top = (f->charheight-h-1)/2; } return c; } //loads the given charidx for the given font, importing from elsewhere if needed. static struct charcache_s *Font_TryLoadGlyph(font_t *f, CHARIDXTYPE charidx) { struct charcache_s *c; #if GEN_CONCHAR_GLYPHS != 0 if (charidx >= 0xe000 && charidx <= 0xe0ff) { int cpos = charidx & 0xff; unsigned int img[64*64], *d; unsigned char *s; int scale; int x,y, ys; qbyte *draw_chars = W_GetLumpName("conchars"); if (draw_chars) { d = img; s = draw_chars + 8*(cpos&15)+128*8*(cpos/16); scale = f->charheight/8; if (scale < 1) scale = 1; if (scale > 64/8) scale = 64/8; for (y = 0; y < 8; y++) { for (ys = 0; ys < scale; ys++) { for (x = 0; x < 8*scale; x++) d[x] = d_8to24rgbtable[s[x/scale]]; d+=8*scale; } s+=128; } c = Font_LoadGlyphData(f, charidx, FT_PIXEL_MODE_RGBA_SA, img, 8*scale, 8*scale, 8*scale*4); if (c) { c->advance = 8*scale; c->left = 0; c->top = 7*scale; } return c; } if ((charidx&0x7f) > 0x20) charidx &= 0x7f; } #endif #ifdef QUAKEHUD if (charidx >= 0xe100 && charidx <= 0xe1ff) { qpic_t *wadimg; qbyte lumptype = 0; unsigned char *src; unsigned int img[64*64]; int nw, nh; int x, y; unsigned int stepx, stepy; unsigned int srcx, srcy; size_t lumpsize = 0; if (charidx-0xe100 >= sizeof(imgs)/sizeof(imgs[0])) wadimg = NULL; else wadimg = W_GetLumpName(imgs[charidx-0xe100], &lumpsize, &lumptype); if (wadimg && lumptype == TYP_QPIC && lumpsize == 8+wadimg->height*wadimg->width) { nh = wadimg->height; nw = wadimg->width; while (nh < f->charheight) { nh *= 2; nw *= 2; } if (nh > f->charheight) { nw = (nw * f->charheight)/nh; nh = f->charheight; } stepy = 0x10000*((float)wadimg->height/nh); stepx = 0x10000*((float)wadimg->width/nw); if (nh > 64) nh = 64; if (nw > 64) nw = 64; srcy = 0; for (y = 0; y < nh; y++) { src = (unsigned char *)(wadimg->data); src += wadimg->width * (srcy>>16); srcy += stepy; srcx = 0; for (x = 0; x < nw; x++) { img[x+y*64] = d_8to24rgbtable[src[srcx>>16]]; srcx += stepx; } } c = Font_LoadGlyphData(f, charidx, FT_PIXEL_MODE_RGBA_SA, img, nw, nh, 64*4); if (c) { c->flags = CHARF_FORCEWHITE; //private glyph colours c->left = 0; c->top = f->charheight - nh; c->advance = nw; return c; } } } #endif /*make tab invisible*/ if (charidx == '\t' || charidx == '\n') { c = &f->chars[charidx/FONTBLOCKSIZE][charidx&FONTBLOCKMASK]; c->left = 0; c->advance = f->charheight; c->top = 0; c->texplane = 0; c->bmx = 0; c->bmy = 0; c->bmw = 0; c->bmh = 0; return c; } if (f->faces) { int file; for (file = 0; file < f->faces; file++) { fontface_t *qface = f->face[file]; #ifdef AVAIL_FREETYPE if (qface->ft.face) { FT_Face face = qface->ft.face; if (qface->ft.activeheight != f->charheight) { qface->ft.activeheight = f->charheight; if (FT_HAS_FIXED_SIZES(face) && !FT_IS_SCALABLE(face)) { //freetype doesn't like scaling these for us, so we have to pick a usable size ourselves. FT_Int best = 0, s; int bestheight = 0, h; for (s = 0; s < qface->ft.face->num_fixed_sizes; s++) { h = qface->ft.face->available_sizes[s].height; //always try to pick the smallest size that is also >= our target size if ((h > bestheight && bestheight < f->charheight) || (h >= f->charheight && h < bestheight)) { bestheight = h; best = s; } } qface->ft.actualsize = qface->ft.face->available_sizes[best].height; pFT_Select_Size(face, best); } else { pFT_Set_Pixel_Sizes(face, 0, f->charheight); qface->ft.actualsize = f->charheight; } } if (charidx == 0xfffe || pFT_Get_Char_Index(face, charidx)) //ignore glyph 0 (undefined) if (pFT_Load_Char(face, charidx, FT_LOAD_RENDER|FT_LOAD_COLOR) == 0) { FT_GlyphSlot slot; FT_Bitmap *bm; slot = face->glyph; bm = &slot->bitmap; if (qface->ft.activeheight!=qface->ft.actualsize) { //I'm just going to assume full-height raster glyphs here. I'm sure I'll be proven wrong some time but w/e. int nw, nh; if (bm->rows) { nh = f->charheight; nw = (bm->width*nh)/bm->rows; } else nw = f->charheight, nh = 0; if (!nw || !nh) c = Font_LoadGlyphData(f, charidx, FT_PIXEL_MODE_GRAY, NULL, nw, nh, 0); else if (bm->pixel_mode == FT_PIXEL_MODE_BGRA) { unsigned int *out = alloca(nw*nh*sizeof(*out)); Image_ResampleTexture(PTI_BGRA8, (void*)bm->buffer, bm->width, bm->rows, out, nw, nh); c = Font_LoadGlyphData(f, charidx, bm->pixel_mode, out, nw, nh, nw*sizeof(*out)); } else if (bm->pixel_mode == FT_PIXEL_MODE_GRAY) { unsigned char *out = alloca(nw*nh*sizeof(*out)); Image_ResampleTexture(PTI_L8, (void*)bm->buffer, bm->width, bm->rows, out, nw, nh); c = Font_LoadGlyphData(f, charidx, bm->pixel_mode, out, nw, nh, nw*sizeof(*out)); } else c = NULL; if (c) { c->advance = nw; c->left = 0; c->top = 0; return c; } } else { c = Font_LoadGlyphData(f, charidx, bm->pixel_mode, bm->buffer, bm->width, bm->rows, bm->pitch); if (c) { c->advance = slot->advance.x >> 6; c->left = slot->bitmap_left; c->top = f->charheight*3/4 - slot->bitmap_top; return c; } } } } else #endif #ifdef HALFLIFEMODELS if (qface->halflife) { size_t glyph = charidx; if (glyph > 0xe000) glyph -= 0xe000; if (glyph < 0x100) { int gw = qface->halflife->chartab[glyph].width; int gh = qface->halflife->fontheight1; qbyte *in = qface->halflife->data + 256 * qface->halflife->chartab[glyph].y + qface->halflife->chartab[glyph].x; qbyte *pal = qface->halflife->data + 256 * qface->halflife->imgheight+2; qbyte *out = alloca(gw*gh*4); int x, y; for (y = 0; y < gh; y++, in += 256-gw) for (x = 0; x < gw; x++, in++) { if (*in==0xff) { out[(x+y*gw)*4+0] = 0; out[(x+y*gw)*4+1] = 0; out[(x+y*gw)*4+2] = 0; out[(x+y*gw)*4+3] = 0; } else { out[(x+y*gw)*4+0] = pal[*in*3+0]; out[(x+y*gw)*4+1] = pal[*in*3+1]; out[(x+y*gw)*4+2] = pal[*in*3+2]; out[(x+y*gw)*4+3] = 0xff; } } if (f->charheight != gh) { int ngw = (gw * f->charheight) / gh; int ngh = f->charheight; qbyte *out2 = alloca(ngw*ngh*4); if (ngw&&ngh) Image_ResampleTexture(PTI_RGBA8, out, gw, gh, out2, ngw, ngh); c = Font_LoadGlyphData(f, charidx, FT_PIXEL_MODE_RGBA, out2, ngw, ngh, ngw*4); gw = ngw; } else c = Font_LoadGlyphData(f, charidx, FT_PIXEL_MODE_RGBA, out, gw, gh, gw*4); if (c) { c->flags = 0; //private glyph colours c->advance = gw; c->left = 0; c->top = 0; return c; } } } else #endif if (qface->horiz.data) { #if 1 size_t maxchar = 256; int gw = qface->horiz.width/maxchar; #else int gw = qface->horiz.height-(qface->horiz.height/12); size_t maxchar = qface->horiz.width / gw; #endif size_t glyph = charidx /* - qface->horiz.firstcodepoint*/; if (glyph < maxchar) { unsigned int *glyphdata = (unsigned int*)qface->horiz.data + glyph*gw; int gh = qface->horiz.height; int gs = qface->horiz.width; unsigned int *out = glyphdata; while (gw >= 1) { int y; gw--; //see if we can strip this column for (y = 0; y < gh; y++) if (glyphdata[gw+y*gs] & 0x00ffffff) break; if (y < gh) { gw++; break; } } if (f->charheight != gh) { int ngw = (gw * f->charheight) / gh; int ngh = f->charheight; int x, y; unsigned int *out2 = alloca(ngw*ngh*4); if (ngw&&ngh) { //we need to repack the input, because Image_ResampleTexture can't handle strides unsigned int *out1 = alloca(gw*gh*4); for (y = 0; y < gh; y++) for (x = 0; x < gw; x++) out1[x+y*gw] = out[x+y*gs]; Image_ResampleTexture(PTI_RGBA8, out1, gw, gh, out2, ngw, ngh); } c = Font_LoadGlyphData(f, charidx, FT_PIXEL_MODE_RGBA, out2, ngw, ngh, ngw*4); gw = ngw; } else c = Font_LoadGlyphData(f, charidx, FT_PIXEL_MODE_RGBA, out, gw, gh, gs*4); if (!gw) //for invisble glyphs (eg: space), we attempt to ensure that there's some substance there. missing spaces is weird. gw = gh/3; if (c) { c->advance = gw; c->left = 0; c->top = 0; return c; } } } } } if (charidx == '\r') return Font_CopyChar(f, charidx|0xe000, charidx); return NULL; } //obtains a cached char, null if not cached static struct charcache_s *Font_GetChar(font_t *f, unsigned int codepoint) { CHARIDXTYPE charidx; struct charcache_s *c; if (codepoint > FONT_MAXCHARS) charidx = 0xfffd; else charidx = codepoint; c = Font_GetCharIfLoaded(f, charidx); if (!c) { if (charidx >= TRACKERFIRST && charidx < TRACKERFIRST+100) { static struct charcache_s tc; tc.texplane = TRACKERIMAGE; fontplanes.trackerimage = Font_GetTrackerImage(charidx-TRACKERFIRST); if (!fontplanes.trackerimage) return Font_GetChar(f, '?'); tc.advance = fontplanes.trackerimage->width * ((float)f->charheight / fontplanes.trackerimage->height); return &tc; } //not cached, can't get. c = Font_TryLoadGlyph(f, charidx); if (!c && charidx >= 0x400 && charidx <= 0x45f) { //apparently there's a lot of russian players out there. //if we just replace all their chars with a '?', they're gonna get pissed. //so lets at least attempt to provide some default mapping that makes sense even if they don't have a full font. //koi8-u is a mapping useful with 7-bit email because the message is still vaugely readable in latin if the high bits get truncated. //not being a language specialist, I'm just going to use that mapping, with the high bit truncated to ascii (which mostly exists in the quake charset). //this exact table is from ezquake. because I'm too lazy to figure out the proper mapping. (beware of triglyphs) static char *wc2koi_table = "?3??4?67??" "??" "??" ">?" "abwgdevzijklmnop" "rstufhc~{}/yx|`q" "ABWGDEVZIJKLMNOP" "RSTUFHC^[]_YX\\@Q" "?#??$?&'??" "??" "??.?"; charidx = wc2koi_table[charidx - 0x400]; if (charidx != '?') { c = Font_GetCharIfLoaded(f, charidx); if (!c) c = Font_TryLoadGlyph(f, charidx); } } if (!c) c = Font_LoadPlaceholderGlyph(f, charidx); if (!c) { charidx = 0xfffd; //unicode's replacement char c = Font_GetCharIfLoaded(f, charidx); if (!c) c = Font_TryLoadGlyph(f, charidx); } if (!c) { charidx = '?'; //meh c = Font_GetCharIfLoaded(f, charidx); if (!c) c = Font_TryLoadGlyph(f, charidx); } } return c; } qboolean Font_LoadHorizontalFont(struct font_s *f, int fheight, const char *fontfilename) { //halflife-style. fontface_t *qface; void *rawdata; qofs_t rawsize; qbyte *rgbadata = NULL; int width=0,height=0; uploadfmt_t format; if (fheight < 1) fheight = 1; //ran out of font slots. if (f->faces == MAX_FACES) return false; for (qface = faces; qface; qface = qface->fnext) { if (!strcmp(qface->name, fontfilename) && qface->horiz.data) { qface->refs++; f->face[f->faces++] = qface; return true; } } rawdata = FS_MallocFile(fontfilename, FS_GAME, &rawsize); if (rawdata) rgbadata = ReadRawImageFile(rawdata, rawsize, &width, &height, &format, true, fontfilename); FS_FreeFile(rawdata); if (rgbadata) { /*success!*/ qface = Z_Malloc(sizeof(*qface)); qface->flink = &faces; qface->fnext = *qface->flink; *qface->flink = qface; if (qface->fnext) qface->fnext->flink = &qface->fnext; qface->horiz.data = rgbadata; qface->horiz.width = width; qface->horiz.height = height; qface->refs++; Q_strncpyz(qface->name, fontfilename, sizeof(qface->name)); f->face[f->faces++] = qface; return true; } return false; } #ifdef AVAIL_FREETYPE qboolean Font_LoadFreeTypeFont(struct font_s *f, int height, const char *fontfilename) { fontface_t *qface; FT_Face face = NULL; FT_Error error; flocation_t loc; void *fbase = NULL; if (!*fontfilename) return false; if (height < 1) height = 1; //ran out of font slots. if (f->faces == MAX_FACES) return false; for (qface = faces; qface; qface = qface->fnext) { if (!strcmp(qface->name, fontfilename) && qface->ft.face) { qface->refs++; f->face[f->faces++] = qface; return true; } } if (!fontlib) { #ifndef FREETYPE_STATIC dllfunction_t ft2funcs[] = { {(void**)&pFT_Init_FreeType, "FT_Init_FreeType"}, {(void**)&pFT_Load_Char, "FT_Load_Char"}, {(void**)&pFT_Get_Char_Index, "FT_Get_Char_Index"}, {(void**)&pFT_Set_Pixel_Sizes, "FT_Set_Pixel_Sizes"}, {(void**)&pFT_Select_Size, "FT_Select_Size"}, {(void**)&pFT_New_Face, "FT_New_Face"}, {(void**)&pFT_New_Memory_Face, "FT_New_Memory_Face"}, {(void**)&pFT_Init_FreeType, "FT_Init_FreeType"}, {(void**)&pFT_Done_Face, "FT_Done_Face"}, {NULL, NULL} }; if (triedtoloadfreetype) return false; triedtoloadfreetype = true; #ifdef _WIN32 fontmodule = Sys_LoadLibrary("libfreetype-6", ft2funcs); if (!fontmodule) fontmodule = Sys_LoadLibrary("freetype6", ft2funcs); #else fontmodule = Sys_LoadLibrary("libfreetype.so.6", ft2funcs); #endif if (!fontmodule) { Con_DPrintf("Couldn't load freetype library.\n"); return false; } #endif error = pFT_Init_FreeType(&fontlib); if (error) { Con_Printf("FT_Init_FreeType failed.\n"); #ifndef FREETYPE_STATIC Sys_CloseLibrary(fontmodule); #endif return false; } /*any other errors leave freetype open*/ } error = FT_Err_Cannot_Open_Resource; if (FS_FLocateFile(fontfilename, FSLF_IFFOUND, &loc) || FS_FLocateFile(va("%s.ttf", fontfilename), FSLF_IFFOUND, &loc)) { if (*loc.rawname && !loc.offset) { fbase = NULL; /*File is directly fopenable with no bias (not in a pk3/pak). Use the system-path form, so we don't have to eat the memory cost*/ error = pFT_New_Face(fontlib, loc.rawname, 0, &face); } else { /*File is inside an archive, we need to read it and pass it as memory (and keep it available)*/ vfsfile_t *f; f = FS_OpenReadLocation(&loc); if (f && loc.len > 0) { fbase = BZ_Malloc(loc.len); VFS_READ(f, fbase, loc.len); VFS_CLOSE(f); error = pFT_New_Memory_Face(fontlib, fbase, loc.len, 0, &face); } } } #if defined(_WIN32) if (error) { static qboolean firsttime = true; static char fontdir[MAX_OSPATH]; if (firsttime) { HRESULT (WINAPI *dSHGetFolderPath) (HWND hwndOwner, int nFolder, HANDLE hToken, DWORD dwFlags, LPTSTR pszPath); dllfunction_t shfolderfuncs[] = { {(void**)&dSHGetFolderPath, "SHGetFolderPathA"}, {NULL, NULL} }; dllhandle_t *shfolder = Sys_LoadLibrary("shfolder.dll", shfolderfuncs); firsttime = false; if (shfolder) { // 0x14 == CSIDL_FONTS if (dSHGetFolderPath(NULL, 0x14, NULL, 0, fontdir) != S_OK) *fontdir = 0; Sys_CloseLibrary(shfolder); } } if (*fontdir) { error = pFT_New_Face(fontlib, va("%s/%s", fontdir, fontfilename), 0, &face); if (error) error = pFT_New_Face(fontlib, va("%s/%s.ttf", fontdir, fontfilename), 0, &face); } } #else if (error) { //eg: /usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf error = pFT_New_Face(fontlib, va("/usr/share/fonts/%s", fontfilename), 0, &face); if (error) error = pFT_New_Face(fontlib, va("/usr/share/fonts/truetype/%s.ttf", fontfilename), 0, &face); if (error) //just to give a chance of the same names working on more than one os, with the right package installed. error = pFT_New_Face(fontlib, va("/usr/share/fonts/truetype/msttcorefonts/%s.ttf", fontfilename), 0, &face); } #endif if (!error) { if (FT_HAS_FIXED_SIZES(face) && !FT_IS_SCALABLE(face)) { height = 0; //will need to rescale manually I guess error = pFT_Select_Size(face, 0); } else error = pFT_Set_Pixel_Sizes(face, 0, height); if (!error) { /*success!*/ qface = Z_Malloc(sizeof(*qface)); qface->flink = &faces; qface->fnext = *qface->flink; *qface->flink = qface; if (qface->fnext) qface->fnext->flink = &qface->fnext; qface->ft.face = face; qface->ft.activeheight = qface->ft.actualsize = height; qface->ft.membuf = fbase; qface->refs++; Q_strncpyz(qface->name, fontfilename, sizeof(qface->name)); f->face[f->faces++] = qface; return true; } } if (error && error != FT_Err_Cannot_Open_Resource) Con_Printf("Freetype error: %i\n", error); if (fbase) BZ_Free(fbase); return false; } #endif static texid_t Font_LoadReplacementConchars(void) { texid_t tex; //q1 replacement tex = R_LoadHiResTexture("gfx/conchars.lmp", NULL, (r_font_linear.ival?IF_LINEAR:IF_NEAREST)|IF_PREMULTIPLYALPHA|IF_LOADNOW|IF_UIPIC|IF_NOMIPMAP|IF_NOGAMMA); TEXDOWAIT(tex); if (TEXLOADED(tex)) return tex; //q2 tex = R_LoadHiResTexture("pics/conchars.pcx", NULL, (r_font_linear.ival?IF_LINEAR:IF_NEAREST)|IF_PREMULTIPLYALPHA|IF_LOADNOW|IF_UIPIC|IF_NOMIPMAP|IF_NOGAMMA); TEXDOWAIT(tex); if (TEXLOADED(tex)) return tex; //q3 tex = R_LoadHiResTexture("gfx/2d/bigchars.tga", NULL, (r_font_linear.ival?IF_LINEAR:IF_NEAREST)|IF_PREMULTIPLYALPHA|IF_LOADNOW|IF_UIPIC|IF_NOMIPMAP|IF_NOGAMMA); TEXDOWAIT(tex); if (TEXLOADED(tex)) return tex; return r_nulltex; } static texid_t Font_LoadQuakeConchars(void) { /*unsigned int i; qbyte *lump; lump = W_SafeGetLumpName ("conchars"); if (lump) { // add ocrana leds if (con_ocranaleds.ival) { if (con_ocranaleds.ival != 2 || QCRC_Block(lump, 128*128) == 798) AddOcranaLEDsIndexed (lump, 128, 128); } for (i=0 ; i<128*128 ; i++) if (lump[i] == 0) lump[i] = 255; // proper transparent color return R_LoadTexture8("charset", 128, 128, (void*)lump, IF_LOADNOW|IF_UIPIC|IF_NOMIPMAP|IF_NOGAMMA, 1); }*/ return r_nulltex; } #ifdef HEXEN2 static texid_t Font_LoadHexen2Conchars(qboolean iso88591) { //gulp... so it's come to this has it? rework the hexen2 conchars into the q1 system. texid_t tex; unsigned int i, x; unsigned char *tempchars; unsigned char *in, *out, *outbuf; FS_LoadFile("gfx/menu/conchars.lmp", (void**)&tempchars); /*hexen2's conchars are arranged 32-wide, 16 high. the upper 8 rows are 256 8859-1 chars the lower 8 rows are a separate set of recoloured 8859-1 chars. if we're loading for the fallback then we're loading this data for quake compatibility, so we grab only the first 4 rows of each set of chars (128 low chars, 128 high chars). if we're loading a proper charset, then we load only the first set of chars, we can recolour the rest anyway (com_parseutf8 will do so anyway). as a final note, parsing iso8859-1 french/german/etc as utf8 will generally result in decoding errors which can gracefully revert to 8859-1 safely. If this premise fails too much, we can always change the parser for different charsets - the engine always uses unicode and thus 8859-1 internally. */ if (tempchars) { outbuf = BZ_Malloc(8*8*256*8); out = outbuf; i = 0; /*read the low chars*/ for (; i < 8*8*1; i+=1) { if (i&(1<<3)) in = tempchars + (i>>3)*16*8*8+(i&7)*32*8 - 256*4+128; else in = tempchars + (i>>3)*16*8*8+(i&7)*32*8; for (x = 0; x < 16*8; x++) *out++ = *in++; } if (iso88591) { /*read the non 8859-1 quake-compat control chars*/ for (; i < 8*8*1 + 16; i+=1) { if (i&(1<<3)) in = tempchars+128*128 + ((i>>3)&7)*16*8*8+(i&7)*32*8 - 256*4+128; else in = tempchars+128*128 + ((i>>3)&7)*16*8*8+(i&7)*32*8; for (x = 0; x < 16*8; x++) *out++ = *in++; } /*read the final low chars (final if 8859-1 anyway)*/ for (; i < 8*8*2; i+=1) { if (i&(1<<3)) in = tempchars + (i>>3)*16*8*8+(i&7)*32*8 - 256*4+128; else in = tempchars + (i>>3)*16*8*8+(i&7)*32*8; for (x = 0; x < 16*8; x++) *out++ = *in++; } } else { /*read the high chars*/ for (; i < 8*8*2; i+=1) { if (i&(1<<3)) in = tempchars+128*128 + ((i>>3)&15)*16*8*8+(i&7)*32*8 - 256*4+128; else in = tempchars+128*128 + ((i>>3)&15)*16*8*8+(i&7)*32*8; for (x = 0; x < 16*8; x++) *out++ = *in++; } } FS_FreeFile(tempchars); // add ocrana leds if (!iso88591 && con_ocranaleds.value && con_ocranaleds.value != 2) AddOcranaLEDsIndexed (outbuf, 128, 128); for (i=0 ; i<128*128 ; i++) if (outbuf[i] == 0) outbuf[i] = 255; // proper transparent color tex = R_LoadTexture8 (iso88591?"gfx/menu/8859-1.lmp":"charset", 128, 128, outbuf, IF_PREMULTIPLYALPHA|IF_LOADNOW|IF_UIPIC|IF_NOMIPMAP|IF_NOGAMMA, 1); Z_Free(outbuf); return tex; } return r_nulltex; } #endif FTE_ALIGN(4) qbyte default_conchar[/*11356*/] = { #include "lhfont.h" }; static void Font_CopyGlyph(int src, int dst, void *data) { int glyphsize = 16; int y; int x; char *srcptr = (char*)data + (src&15)*glyphsize*4 + (src>>4)*glyphsize*256*4; char *dstptr = (char*)data + (dst&15)*glyphsize*4 + (dst>>4)*glyphsize*256*4; for (y = 0; y < glyphsize; y++) { for (x = 0; x < glyphsize; x++) { dstptr[x*4+0] = srcptr[x*4+0]; dstptr[x*4+1] = srcptr[x*4+1]; dstptr[x*4+2] = srcptr[x*4+2]; dstptr[x*4+3] = srcptr[x*4+3]; } dstptr += 256*4; srcptr += 256*4; } } static texid_t Font_LoadFallbackConchars(void) { texid_t tex; int width, height; unsigned int i; qbyte *lump; uploadfmt_t format; lump = ReadTargaFile(default_conchar, sizeof(default_conchar), &width, &height, &format, false, PTI_INVALID); if (!lump || (format != PTI_RGBX8 && format != PTI_RGBA8 && format != PTI_LLLX8)) Sys_Error("Corrupt internal drawchars (%i)", format); /*convert greyscale to alpha*/ for (i = 0; i < width*height; i++) { lump[i*4+3] = lump[i*4]; lump[i*4+0] = 255; lump[i*4+1] = 255; lump[i*4+2] = 255; } if (width == 256 && height == 256) { //make up some scroll-bar/download-progress-bar chars, so that webgl doesn't look so buggy with the initial pak file(s). Font_CopyGlyph('[', 128, lump); Font_CopyGlyph('-', 129, lump); Font_CopyGlyph(']', 130, lump); Font_CopyGlyph('|', 131, lump); Font_CopyGlyph('>', 13, lump); } tex = Image_GetTexture("charset", NULL, IF_PREMULTIPLYALPHA|IF_LOADNOW|IF_UIPIC|IF_NOMIPMAP|IF_NOGAMMA, (void*)lump, NULL, width, height, PTI_RGBA8); BZ_Free(lump); return tex; } enum fontfmt_e { FMT_AUTO, //freetype, or quake FMT_QUAKE, //first is default FMT_ISO88591, //latin-1 (first 256 chars of unicode too, c1 glyphs are usually invisible) FMT_WINDOWS1252,//variation of latin-1 with extra glyphs FMT_KOI8U, //image is 16*16 koi8-u codepage. FMT_HORIZONTAL, //unicode, charcount=width/(height-2). single strip of chars, like halflife. }; /*loads a fallback image. not allowed to fail (use syserror if needed)*/ static texid_t Font_LoadDefaultConchars(enum fontfmt_e *fmt) { texid_t tex; tex = Font_LoadReplacementConchars(); if (TEXLOADED(tex)) return tex; tex = Font_LoadQuakeConchars(); if (tex && tex->status == TEX_LOADING) COM_WorkerPartialSync(tex, &tex->status, TEX_LOADING); if (TEXLOADED(tex)) return tex; #ifdef HEXEN2 tex = Font_LoadHexen2Conchars(true); if (tex && tex->status == TEX_LOADING) COM_WorkerPartialSync(tex, &tex->status, TEX_LOADING); if (TEXLOADED(tex)) { *fmt = FMT_ISO88591; return tex; } #endif tex = Font_LoadFallbackConchars(); if (tex && tex->status == TEX_LOADING) COM_WorkerPartialSync(tex, &tex->status, TEX_LOADING); if (TEXLOADED(tex)) { *fmt = FMT_QUAKE; return tex; } Sys_Error("Unable to load any conchars\n"); } typedef struct { short width; short height; short leftoffset; // pixels to the left of origin short topoffset; // pixels below the origin int columnofs[1]; } doompatch_t; typedef struct { unsigned char topdelta; // -1 is the last post in a column unsigned char length; // length data bytes follows } doomcolumn_t; void Doom_ExpandPatch(doompatch_t *p, unsigned char *b, int stride) { doomcolumn_t *col; unsigned char *src, *dst; int x, y; for (x = 0; x < p->width; x++) { col = (doomcolumn_t *)((unsigned char *)p + p->columnofs[x]); while(col->topdelta != 0xff) { //exploit protection if (col->length + col->topdelta > p->height) break; src = (unsigned char *)col + 2; /*why 3? why not, I suppose*/ dst = b + stride*col->topdelta; for (y = 0; y < col->length; y++) { *dst = *src++; dst += stride; } src++; col = (doomcolumn_t *)((unsigned char*)col + col->length + 4); } b++; } } //creates a new font object from the given file, with each text row with the given height. //width is implicit and scales with height and choice of font. struct font_s *Font_LoadFont(const char *fontfilename, float vheight) { struct font_s *f; int i = 0; int defaultplane; char *aname; char *parms; int height = ((vheight * vid.rotpixelheight)/vid.height) + 0.5; char facename[MAX_QPATH*12]; struct charcache_s *c; float aspect = 1; enum fontfmt_e fmt = FMT_AUTO; Q_strncpyz(facename, fontfilename, sizeof(facename)); aname = strstr(facename, ":"); if (aname) *aname++ = 0; parms = strstr(facename, "?"); if (parms) *parms++ = 0; f = Z_Malloc(sizeof(*f)); f->charheight = height; Q_strncpyz(f->name, fontfilename, sizeof(f->name)); switch(M_GameType()) { case MGT_QUAKE2: VectorSet(f->alttint, 0.44, 1.0, 0.2); break; default: VectorSet(f->alttint, 1.16, 0.54, 0.41); break; } VectorSet(f->tint, 1, 1, 1); fontfilename = facename; if (parms) { while (*parms) { if (!strncmp(parms, "col=", 4)) { char *t = parms+4; f->tint[0] = strtod(t, &t); if (*t == ',') t++; if (*t == ' ') t++; f->tint[1] = strtod(t, &t); if (*t == ',') t++; if (*t == ' ') t++; f->tint[2] = strtod(t, &t); parms = t; } if (!strncmp(parms, "fmt=", 4)) { char *t = parms+4; fmt = 0; if (*t == 'q') fmt = FMT_QUAKE; else if (*t == 'l') fmt = FMT_ISO88591; else if (*t == 'w') fmt = FMT_WINDOWS1252; else if (*t == 'k') fmt = FMT_KOI8U; else if (*t == 'h') fmt = FMT_HORIZONTAL; } if (!strncmp(parms, "aspect=", 7)) { char *t = parms+7; aspect = strtod(t, &t); parms = t; } while(*parms && *parms != '&') parms++; if (*parms == '&') { parms++; continue; } } } if (vid.flags&VID_SRGBAWARE) { f->tint[0] = M_SRGBToLinear(f->tint[0], 1); f->tint[1] = M_SRGBToLinear(f->tint[1], 1); f->tint[2] = M_SRGBToLinear(f->tint[2], 1); f->alttint[0] = M_SRGBToLinear(f->alttint[0], 1); f->alttint[1] = M_SRGBToLinear(f->alttint[1], 1); f->alttint[2] = M_SRGBToLinear(f->alttint[2], 1); } #ifdef PACKAGE_DOOMWAD if (!*fontfilename) { unsigned char buf[PLANEWIDTH*PLANEHEIGHT]; int i; int x=0,y=0,h=0; doompatch_t *dp; memset(buf, 0, sizeof(buf)); for (i = '!'; i <= '_'; i++) { dp = NULL; FS_LoadFile(va("wad/stcfn%.3d", i), (void**)&dp); if (!dp) break; /*make sure it can fit*/ if (x + dp->width > PLANEWIDTH) { x = 0; y += h; h = 0; } f->chars[i].advance = dp->width; /*this is how much line space the char takes*/ f->chars[i].left = -dp->leftoffset; f->chars[i].top = -dp->topoffset; f->chars[i].nextchar = 0; f->chars[i].pad = 0; f->chars[i].texplane = SINGLEPLANE; f->chars[i].bmx = x; f->chars[i].bmy = y; f->chars[i].bmh = dp->height; f->chars[i].bmw = dp->width; Doom_ExpandPatch(dp, &buf[y*PLANEWIDTH + x], PLANEWIDTH); x += dp->width; if (dp->height > h) { h = dp->height; if (h > f->charheight) f->charheight = h; } FS_FreeFile(dp); } /*if all loaded okay, replicate the chars to the quake-compat range (both white+red chars)*/ if (i == '_'+1) { //doom doesn't have many chars, so make sure the lower case chars exist. for (i = 'a'; i <= 'z'; i++) f->chars[i] = f->chars[i-'a'+'A']; //no space char either f->chars[' '].advance = 8; f->singletexture = R_LoadTexture8("doomfont", PLANEWIDTH, PLANEHEIGHT, buf, 0, true); for (i = 0xe000; i <= 0xe0ff; i++) { f->chars[i] = f->chars[toupper(i&0x7f)]; } return f; } } #endif #ifdef HEXEN2 if (!strcmp(fontfilename, "gfx/tinyfont")) { unsigned int *img; int x, y; size_t lumpsize; qbyte lumptype; unsigned char *w = W_GetLumpName(fontfilename+4, &lumpsize, &lumptype); if (!w || lumpsize != 32*128 || lumptype != 'D') { Z_Free(f); return NULL; } img = Z_Malloc(PLANEWIDTH*PLANEWIDTH*4); for (y = 0; y < 32; y++) for (x = 0; x < 128; x++) img[x + y*PLANEWIDTH] = w[x + y*128]?d_8to24rgbtable[w[x + y*128]]:0; f->singletexture = R_LoadTexture("tinyfont",PLANEWIDTH,PLANEWIDTH,TF_RGBA32,img,IF_PREMULTIPLYALPHA|IF_UIPIC|IF_NOPICMIP|IF_NOMIPMAP); if (f->singletexture->status == TEX_LOADING) COM_WorkerPartialSync(f->singletexture, &f->singletexture->status, TEX_LOADING); Z_Free(img); for (i = 0x00; i <= 0xff; i++) { c = Font_GetCharStore(f, i); c->advance = (height*3)/4; c->left = 0; c->top = 0; c->nextchar = 0; //these chars are not linked in c->texplane = BITMAPPLANE; /*if its a 'raster' font, don't use the default chars, always use the raster images*/ if (i >= 'a' && i <= 'z') { c->bmx = ((i - 64)&15)*8; c->bmy = ((i - 64)/16)*8; c->bmh = 8; c->bmw = 8; } else if (i >= 32 && i < 96) { c->bmx = ((i - 32)&15)*8; c->bmy = ((i - 32)/16)*8; c->bmh = 8; c->bmw = 8; } else { c->bmh = 0; c->bmw = 0; c->bmx = 0; c->bmy = 0; } Font_CopyChar(f, i, i|0xe0ff); } return f; } #endif if (aname) { if (!strncmp(aname, "?col=", 5)) { char *t = aname+5; f->alttint[0] = strtod(t, &t); if (*t == ',') t++; if (*t == ' ') t++; f->alttint[1] = strtod(t, &t); if (*t == ',') t++; if (*t == ' ') t++; f->alttint[2] = strtod(t, &t); } else { f->alt = Font_LoadFont(aname, vheight); if (f->alt) { VectorCopy(f->alt->tint, f->alttint); VectorCopy(f->alt->tint, f->alt->alttint); } } } { const char *start; start = fontfilename; for(;;) { char *end = strchr(start, ','); if (end) *end = 0; if (fmt == FMT_HORIZONTAL) Font_LoadHorizontalFont(f, height, start); #ifdef AVAIL_FREETYPE else if (fmt == FMT_AUTO) Font_LoadFreeTypeFont(f, height, start); #endif if (end) { *end = ','; start = end+1; } else break; } } #ifdef HALFLIFEMODELS if (!f->faces) { if (f->faces < MAX_FACES) { size_t lumpsize; qbyte lumptype = 0; void *lumpdata = NULL; if ((!lumpdata || lumptype != TYP_HLFONT) && *fontfilename) lumpdata = W_GetLumpName(fontfilename, &lumpsize, &lumptype); if (!lumpdata || lumptype != TYP_HLFONT) lumpdata = W_GetLumpName("conchars", &lumpsize, &lumptype); if (lumpdata && lumptype == TYP_HLFONT) { fontface_t *fa = Z_Malloc(sizeof(*fa)); fa->halflife = lumpdata; fa->flink = &fa->fnext; fa->refs = 1; f->face[f->faces++] = fa; // f->charheight = fa->halflife->fontheight1; //force the font to a specific size. return f; } } } #endif if (!f->faces) { //default to only map the ascii-compatible chars from the quake font. if (*fontfilename) { f->singletexture = R_LoadHiResTexture(fontfilename, "fonts:charsets", IF_PREMULTIPLYALPHA|(r_font_linear.ival?IF_LINEAR:IF_NEAREST)|IF_UIPIC|IF_NOPICMIP|IF_NOMIPMAP); if (f->singletexture->status == TEX_LOADING) COM_WorkerPartialSync(f->singletexture, &f->singletexture->status, TEX_LOADING); } } defaultplane = INVALIDPLANE;/*assume the bitmap plane - don't use the fallback as people don't think to use com_parseutf8*/ if (TEXLOADED(f->singletexture)) defaultplane = BITMAPPLANE; else if (TEXLOADED(fontplanes.defaultfont)) defaultplane = DEFAULTPLANE; if (defaultplane == INVALIDPLANE) { if (!TEXLOADED(fontplanes.defaultfont)) { fontplanes.defaultfont = Font_LoadDefaultConchars(&fmt); } #ifdef HEXEN2 if (!strcmp(fontfilename, "gfx/hexen2")) { f->singletexture = Font_LoadHexen2Conchars(false); defaultplane = DEFAULTPLANE; } #endif if (!TEXLOADED(f->singletexture)) f->singletexture = fontplanes.defaultfont; if (TEXLOADED(f->singletexture)) defaultplane = BITMAPPLANE; else if (TEXLOADED(fontplanes.defaultfont)) defaultplane = DEFAULTPLANE; } if (defaultplane != INVALIDPLANE) { if (fmt==FMT_AUTO) fmt=FMT_QUAKE; if (!f->faces) { static const unsigned short iso88591[] = { 0x80,0x81,0x82,0x83, 0x84,0x85,0x86,0x87, 0x88,0x89,0x8a,0x8b, 0x8c,0x8d,0x8e,0x8f, 0x90,0x91,0x92,0x93, 0x94,0x95,0x96,0x97, 0x98,0x99,0x9a,0x9b, 0x9c,0x9d,0x9e,0x9f}; static const unsigned short win1252[] = { 0x20ac, 0x81,0x201a,0x0192, 0x201e,0x2026,0x2020,0x2021, 0x02c6,0x2030,0x0160,0x2039, 0x0152, 0x8d,0x017d, 0x8f, 0x90,0x2018,0x2019,0x101c, 0x201d,0x2022,0x2013,0x2014, 0x02dc,0x2122,0x0161,0x203a, 0x0153, 0x9d,0x017e,0x0178}; static const unsigned short koi8u[] = { 0x2500,0x2502,0x250C,0x2510, 0x2514,0x2518,0x251C,0x2524, 0x252C,0x2534,0x253C,0x2580, 0x2584,0x2588,0x258C,0x2590, 0x2591,0x2592,0x2593,0x2320, 0x25A0,0x2219,0x221A,0x2248, 0x2264,0x2265,0x00A0,0x2321, 0x00B0,0x00B2,0x00B7,0x00F7, 0x2550,0x2551,0x2552,0x0451, 0x0454,0x2554,0x0456,0x0457, 0x2557,0x2558,0x2559,0x255A, 0x255B,0x0491,0x255D,0x255E, 0x255F,0x2560,0x2561,0x0401, 0x0404,0x2563,0x0406,0x0407, 0x2566,0x2567,0x2568,0x2569, 0x256A,0x0490,0x256C,0x00A9, 0x044E,0x0430,0x0431,0x0446, 0x0434,0x0435,0x0444,0x0433, 0x0445,0x0438,0x0439,0x043A, 0x043B,0x043C,0x043D,0x043E, 0x043F,0x044F,0x0440,0x0441, 0x0442,0x0443,0x0436,0x0432, 0x044C,0x044B,0x0437,0x0448, 0x044D,0x0449,0x0447,0x044A, 0x042E,0x0410,0x0411,0x0426, 0x0414,0x0415,0x0424,0x0413, 0x0425,0x0418,0x0419,0x041A, 0x041B,0x041C,0x041D,0x041E, 0x041F,0x042F,0x0420,0x0421, 0x0422,0x0423,0x0416,0x0412, 0x042C,0x042B,0x0417,0x0428, 0x042D,0x0429,0x0427,0x042A}; const unsigned short *c1; unsigned int c1size; if (fmt == FMT_WINDOWS1252) { //some tools use these extra ones (latin-1 has no visible c1 entries) c1 = win1252; c1size = countof(win1252); } else if (fmt == FMT_KOI8U) { //lots of russians in the quake scene c1 = koi8u; c1size = countof(koi8u); } else { c1 = iso88591; c1size = countof(iso88591); } c1size += 128; /*force it to load, even if there's nothing there*/ for (i = ((fmt==FMT_QUAKE)?32:0); i < ((fmt==FMT_QUAKE)?128:256); i++) { if (i >= 128 && i < c1size) c = Font_GetCharStore(f, c1[i-128]); else c = Font_GetCharStore(f, i); c->advance = f->charheight * aspect; c->bmh = PLANEWIDTH/16; c->bmw = PLANEWIDTH/16; c->bmx = (i&15)*(PLANEWIDTH/16); c->bmy = (i/16)*(PLANEWIDTH/16); c->left = 0; c->top = 0; c->nextchar = 0; //these chars are not linked in c->texplane = defaultplane; } } if (fmt == FMT_QUAKE) { /*pack the default chars into it*/ for (i = 0xe000; i <= 0xe0ff; i++) { c = Font_GetCharStore(f, i); c->advance = f->charheight * aspect; c->bmh = PLANEWIDTH/16; c->bmw = PLANEWIDTH/16; c->bmx = ((i&15))*(PLANEWIDTH/16); c->bmy = ((i&0xf0)/16)*(PLANEWIDTH/16); c->left = 0; c->top = 0; c->nextchar = 0; //these chars are not linked in c->texplane = defaultplane; } } } return f; } //removes a font from memory. void Font_Free(struct font_s *f) { size_t i; struct charcache_s **link, *c, *valid; if (!f) return; //kill the alt font first. if (f->alt) { Font_Free(f->alt); f->alt = NULL; } valid = NULL; //walk all chars, unlinking any that appear to be within this font's char cache for (link = &fontplanes.oldestchar; *link; ) { c = *link; if (f->chars[c->block] && c >= f->chars[c->block] && c <= f->chars[c->block] + FONTBLOCKSIZE) { c = c->nextchar; if (!c) fontplanes.newestchar = valid; *link = c; } else { valid = c; link = &c->nextchar; } } while(f->faces --> 0) { fontface_t *qface = f->face[f->faces]; qface->refs--; if (!qface->refs) { #ifdef AVAIL_FREETYPE if (qface->ft.face) pFT_Done_Face(qface->ft.face); if (qface->ft.membuf) BZ_Free(qface->ft.membuf); #endif *qface->flink = qface->fnext; if (qface->fnext) qface->fnext->flink = qface->flink; Z_Free(qface); } } for (i = 0; i < FONTBLOCKS; i++) if (f->chars[i]) Z_Free(f->chars[i]); Z_Free(f); } //maps a given virtual screen coord to a pixel coord, which matches the font's height/width values void Font_BeginString(struct font_s *font, float vx, float vy, int *px, int *py) { if (R2D_Flush && (R2D_Flush != Font_Flush || curfont != font || font_be_flags != r2d_be_flags)) R2D_Flush(); R2D_Flush = Font_Flush; font_be_flags = r2d_be_flags; curfont = font; *px = (vx*(int)vid.rotpixelwidth) / (float)vid.width; *py = (vy*(int)vid.rotpixelheight) / (float)vid.height; curfont_scale[0] = curfont->charheight; curfont_scale[1] = curfont->charheight; curfont_scaled = false; } void Font_Transform(float vx, float vy, int *px, int *py) { if (px) *px = (vx*(int)vid.rotpixelwidth) / (float)vid.width; if (py) *py = (vy*(int)vid.rotpixelheight) / (float)vid.height; } void Font_BeginScaledString(struct font_s *font, float vx, float vy, float szx, float szy, float *px, float *py) { if (R2D_Flush && (R2D_Flush != Font_Flush || curfont != font || font_be_flags != r2d_be_flags)) R2D_Flush(); R2D_Flush = Font_Flush; font_be_flags = r2d_be_flags; curfont = font; *px = (vx*(float)vid.rotpixelwidth) / (float)vid.width; *py = (vy*(float)vid.rotpixelheight) / (float)vid.height; //now that its in pixels, clamp it so the text is at least consistant with its position. //an individual char may end straddling a pixel boundary, but at least the pixels won't jiggle around as the text moves. *px = (int)*px; *py = (int)*py; if ((int)(szx * vid.rotpixelheight/vid.height) == curfont->charheight && (int)(szy * vid.rotpixelheight/vid.height) == curfont->charheight) curfont_scaled = false; else curfont_scaled = true; curfont_scale[0] = (szx * (float)vid.rotpixelheight) / (curfont->charheight * (float)vid.height); curfont_scale[1] = (szy * (float)vid.rotpixelheight) / (curfont->charheight * (float)vid.height); } void Font_EndString(struct font_s *font) { // Font_Flush(); // curfont = NULL; R2D_Flush = font_foremesh.numindexes?Font_Flush:NULL; } //obtains the font's row height (each row of chars should be drawn using this increment) int Font_CharHeight(void) { return curfont->charheight; } int Font_CharPHeight(struct font_s *font) { return font->charheight; } float Font_CharVHeight(struct font_s *font) { return ((float)font->charheight * vid.height)/vid.rotpixelheight; } //obtains the font's row height (each row of chars should be drawn using this increment) float Font_CharScaleHeight(void) { return curfont->charheight * curfont_scale[1]; } int Font_TabWidth(int x) { int tabwidth = Font_CharWidth(CON_WHITEMASK, ' '); tabwidth *= 8; x++; x = x + ((tabwidth - (x % tabwidth)) % tabwidth); return x; } /* This is where the character ends. Note: this function supports tabs - x must always be based off 0, with Font_LineDraw actually used to draw the line. */ int Font_CharEndCoord(struct font_s *font, int x, unsigned int charflags, unsigned int codepoint) { struct charcache_s *c; if (charflags&CON_HIDDEN) return x; if (codepoint == '\t') return Font_TabWidth(x); if ((charflags & CON_2NDCHARSETTEXT) && font->alt) font = font->alt; c = Font_GetChar(font, codepoint); if (!c) { return x+0; } return x+c->advance; } //obtains the width of a character from a given font. This is how wide it is. The next char should be drawn at x + result. //FIXME: this function cannot cope with tab and should not be used. int Font_CharWidth(unsigned int charflags, unsigned int codepoint) { struct charcache_s *c; struct font_s *font = curfont; if (charflags&CON_HIDDEN) return 0; if ((charflags & CON_2NDCHARSETTEXT) && font->alt) font = font->alt; c = Font_GetChar(curfont, codepoint); if (!c) { return 0; } return c->advance; } //obtains the width of a character from a given font. This is how wide it is. The next char should be drawn at x + result. //FIXME: this function cannot cope with tab and should not be used. float Font_CharScaleWidth(unsigned int charflags, unsigned int codepoint) { struct charcache_s *c; struct font_s *font = curfont; if (charflags&CON_HIDDEN) return 0; if ((charflags & CON_2NDCHARSETTEXT) && font->alt) font = font->alt; c = Font_GetChar(curfont, codepoint); if (!c) { return 0; } return c->advance * curfont_scale[0]; } conchar_t *Font_DecodeReverse(conchar_t *start, conchar_t *stop, unsigned int *codeflags, unsigned int *codepoint) { if (start <= stop) { *codeflags = 0; *codepoint = 0; return stop; } start--; if (start > stop && start[-1] & CON_LONGCHAR) if (!(start[-1] & CON_RICHFORECOLOUR)) { start--; *codeflags = start[1]; *codepoint = ((start[0] & CON_CHARMASK)<<16) | (start[1] & CON_CHARMASK); return start; } *codeflags = start[0]; *codepoint = start[0] & CON_CHARMASK; return start; } //for a given font, calculate the line breaks and word wrapping for a block of text //start+end are the input string //starts+ends are an array of line start and end points, which have maxlines elements. //(end is the terminator, null or otherwise) //maxpixelwidth is the width of the display area in pixels int Font_LineBreaks(conchar_t *start, conchar_t *end, int maxpixelwidth, int maxlines, conchar_t **starts, conchar_t **ends) { conchar_t *l, *bt, *n; int px; int foundlines = 0; struct font_s *font = curfont; unsigned int codeflags, codepoint; while (start < end) { // scan the width of the line for (px=0, l=start ; px <= maxpixelwidth; ) { if (l >= end) break; n = Font_Decode(l, &codeflags, &codepoint); if (!(codeflags & CON_HIDDEN) && (codepoint == '\n' || codepoint == '\v')) break; px = Font_CharEndCoord(font, px, codeflags, codepoint); l = n; } //if we did get to the end if (px > maxpixelwidth) { bt = l; //backtrack until we find a space for(;;) { n = Font_DecodeReverse(l, start, &codeflags, &codepoint); if (codepoint > ' ') l = n; else break; } if (l == start && bt>start) l = Font_DecodeReverse(bt, start, &codeflags, &codepoint); } starts[foundlines] = start; ends[foundlines] = l; foundlines++; if (foundlines == maxlines) break; start=l; if (start == end) break; if ((*start&(CON_CHARMASK|CON_HIDDEN)) == '\n' || (*start&(CON_CHARMASK|CON_HIDDEN)) == '\v') start++; // skip the \n } return foundlines; } int Font_LineWidth(conchar_t *start, conchar_t *end) { //fixme: does this do the right thing with tabs? int x = 0; struct font_s *font = curfont; unsigned int codeflags, codepoint; for (; start < end; ) { start = Font_Decode(start, &codeflags, &codepoint); x = Font_CharEndCoord(font, x, codeflags, codepoint); } return x; } float Font_LineScaleWidth(conchar_t *start, conchar_t *end) { int x = 0; struct font_s *font = curfont; unsigned int codeflags, codepoint; while(start < end) { start = Font_Decode(start, &codeflags, &codepoint); x = Font_CharEndCoord(font, x, codeflags, codepoint); } return x * curfont_scale[0]; } void Font_LineDraw(int x, int y, conchar_t *start, conchar_t *end) { int lx = 0; struct font_s *font = curfont; unsigned int codeflags, codepoint; for (; start < end; ) { start = Font_Decode(start, &codeflags, &codepoint); Font_DrawChar(x+lx, y, codeflags, codepoint); lx = Font_CharEndCoord(font, lx, codeflags, codepoint); } } conchar_t *Font_CharAt(int x, conchar_t *start, conchar_t *end) { int lx = 0, nx; struct font_s *font = curfont; unsigned int codeflags, codepoint; conchar_t *nc; for (; start < end; lx = nx, start = nc) { nc = Font_Decode(start, &codeflags, &codepoint); nx = Font_CharEndCoord(font, lx, codeflags, codepoint); if (x >= lx && x < nx) return start; } return NULL; } /*Note: *all* strings after the current one will inherit the same colour, until one changes it explicitly correct usage of this function thus requires calling this with 1111 before Font_EndString*/ void Font_InvalidateColour(vec4_t newcolour) { if (font_foretint[0] == newcolour[0] && font_foretint[1] == newcolour[1] && font_foretint[2] == newcolour[2] && font_foretint[3] == newcolour[3]) return; if (font_colourmask & CON_NONCLEARBG) { if (R2D_Flush) R2D_Flush(); R2D_Flush = Font_Flush; } font_colourmask = CON_WHITEMASK; VectorScale(newcolour, newcolour[3], font_foretint); font_foretint[3] = newcolour[3]; Vector4Scale(font_foretint, 255, font_forecolour.rgba); font_backcolour.rgba[3] = 0; /*Any drawchars that are now drawn will get the forced colour*/ } //draw a character from the current font at a pixel location. int Font_DrawChar(int px, int py, unsigned int charflags, unsigned int codepoint) { struct charcache_s *c; float s0, s1; float t0, t1; float nextx; float sx, sy, sw, sh; int col; int v; struct font_s *font = curfont; #ifdef D3D11QUAKE float dxbias = 0;//(qrenderer == QR_DIRECT3D11)?0.5:0; #else #define dxbias 0 #endif if (charflags & CON_HIDDEN) return px; if (charflags & CON_2NDCHARSETTEXT) { if (font->alt) { font = font->alt; // charflags &= ~CON_2NDCHARSETTEXT; } else if ((codepoint) >= 0xe000 && (codepoint) <= 0xe0ff) charflags &= ~CON_2NDCHARSETTEXT; //don't double-dip } //crash if there is no current font. c = Font_GetChar(font, codepoint); if (!c) return px; nextx = px + c->advance; if (codepoint == '\t') return Font_TabWidth(px); if (codepoint == ' ' && (charflags & (CON_RICHFORECOLOUR|CON_NONCLEARBG)) != CON_NONCLEARBG) return nextx; /* if (charcode & CON_BLINKTEXT) { if (!cl_noblink.ival) if ((int)(realtime*3) & 1) return nextx; } */ if (charflags & CON_RICHFORECOLOUR) { col = charflags & (CON_2NDCHARSETTEXT|CON_BLINKTEXT|CON_RICHFORECOLOUR|(0xfff<>CON_RICHRSHIFT)&0xf)*0x11; rgba[1] = ((col>>CON_RICHGSHIFT)&0xf)*0x11; rgba[2] = ((col>>CON_RICHBSHIFT)&0xf)*0x11; rgba[3] = 255; font_backcolour.c = 0; if (charflags & CON_2NDCHARSETTEXT) { rgba[0] *= font->alttint[0]; rgba[1] *= font->alttint[1]; rgba[2] *= font->alttint[2]; } else { rgba[0] *= font->tint[0]; rgba[1] *= font->tint[1]; rgba[2] *= font->tint[2]; } rgba[0] *= font_foretint[0]; rgba[1] *= font_foretint[1]; rgba[2] *= font_foretint[2]; rgba[3] *= font_foretint[3]; if (charflags & CON_BLINKTEXT) { float a = (sin(realtime*3)+1)*0.4 + 0.2; Vector4Scale(rgba, a, rgba); } font_forecolour.rgba[0] = min(rgba[0], 255); font_forecolour.rgba[1] = min(rgba[1], 255); font_forecolour.rgba[2] = min(rgba[2], 255); font_forecolour.rgba[3] = min(rgba[3], 255); } } else { col = charflags & (CON_2NDCHARSETTEXT|CON_NONCLEARBG|CON_BGMASK|CON_FGMASK|CON_HALFALPHA|CON_BLINKTEXT); if (col != font_colourmask) { vec4_t rgba; if ((col ^ font_colourmask) & CON_NONCLEARBG) { Font_Flush(); R2D_Flush = Font_Flush; } font_colourmask = col; col = (charflags&CON_FGMASK)>>CON_FGSHIFT; if(charflags & CON_HALFALPHA) { rgba[0] = consolecolours[col].fr*0x7f; rgba[1] = consolecolours[col].fg*0x7f; rgba[2] = consolecolours[col].fb*0x7f; rgba[3] = 0x7f; } else { rgba[0] = consolecolours[col].fr*255; rgba[1] = consolecolours[col].fg*255; rgba[2] = consolecolours[col].fb*255; rgba[3] = 255; } if (vid.flags&VID_SRGBAWARE) { rgba[0] = M_SRGBToLinear(rgba[0], 255); rgba[1] = M_SRGBToLinear(rgba[1], 255); rgba[2] = M_SRGBToLinear(rgba[2], 255); } col = (charflags&CON_BGMASK)>>CON_BGSHIFT; if (charflags & CON_NONCLEARBG) { font_backcolour.rgba[0] = consolecolours[col].fr*255; font_backcolour.rgba[1] = consolecolours[col].fg*255; font_backcolour.rgba[2] = consolecolours[col].fb*255; font_backcolour.rgba[3] = (charflags & CON_NONCLEARBG)?0xc0:0; } else font_backcolour.c = 0; if (charflags & CON_2NDCHARSETTEXT) { rgba[0] *= font->alttint[0]; rgba[1] *= font->alttint[1]; rgba[2] *= font->alttint[2]; } else { rgba[0] *= font->tint[0]; rgba[1] *= font->tint[1]; rgba[2] *= font->tint[2]; } rgba[0] *= font_foretint[0]; rgba[1] *= font_foretint[1]; rgba[2] *= font_foretint[2]; rgba[3] *= font_foretint[3]; if (charflags & CON_BLINKTEXT) { float a = (sin(realtime*3)+1)*0.4 + 0.2; Vector4Scale(rgba, a, rgba); } font_forecolour.rgba[0] = min(rgba[0], 255); font_forecolour.rgba[1] = min(rgba[1], 255); font_forecolour.rgba[2] = min(rgba[2], 255); font_forecolour.rgba[3] = min(rgba[3], 255); } } s0 = (float)c->bmx/PLANEWIDTH; t0 = (float)c->bmy/PLANEWIDTH; s1 = (float)(c->bmx+c->bmw)/PLANEWIDTH; t1 = (float)(c->bmy+c->bmh)/PLANEWIDTH; switch(c->texplane) { case TRACKERIMAGE: s0 = t0 = 0; s1 = t1 = 1; sx = ((px+c->left + dxbias)*(int)vid.width) / (float)vid.rotpixelwidth; sy = ((py+c->top + dxbias)*(int)vid.height) / (float)vid.rotpixelheight; sw = (c->advance*vid.width) / (float)vid.rotpixelwidth; sh = (font->charheight*vid.height) / (float)vid.rotpixelheight; v = Font_BeginChar(fontplanes.trackerimage); break; case DEFAULTPLANE: sx = ((px+c->left + dxbias)*(int)vid.width) / (float)vid.rotpixelwidth; sy = ((py+c->top + dxbias)*(int)vid.height) / (float)vid.rotpixelheight; sw = ((c->advance)*vid.width) / (float)vid.rotpixelwidth; sh = ((font->charheight)*vid.height) / (float)vid.rotpixelheight; v = Font_BeginChar(fontplanes.defaultfont); break; case BITMAPPLANE: sx = ((px+c->left + dxbias)*(int)vid.width) / (float)vid.rotpixelwidth; sy = ((py+c->top + dxbias)*(int)vid.height) / (float)vid.rotpixelheight; sw = ((c->advance)*vid.width) / (float)vid.rotpixelwidth; sh = ((font->charheight)*vid.height) / (float)vid.rotpixelheight; v = Font_BeginChar(font->singletexture); break; case SINGLEPLANE: sx = ((px+c->left + dxbias)*(int)vid.width) / (float)vid.rotpixelwidth; sy = ((py+c->top + dxbias)*(int)vid.height) / (float)vid.rotpixelheight; sw = ((c->bmw)*vid.width) / (float)vid.rotpixelwidth; sh = ((c->bmh)*vid.height) / (float)vid.rotpixelheight; v = Font_BeginChar(font->singletexture); break; default: sx = ((px+c->left + dxbias)*(int)vid.width) / (float)vid.rotpixelwidth; sy = ((py+c->top + dxbias)*(int)vid.height) / (float)vid.rotpixelheight; sw = ((c->bmw)*vid.width) / (float)vid.rotpixelwidth; sh = ((c->bmh)*vid.height) / (float)vid.rotpixelheight; v = Font_BeginChar(fontplanes.texnum[c->texplane]); break; } font_texcoord[v+0][0] = s0; font_texcoord[v+0][1] = t0; font_texcoord[v+1][0] = s1; font_texcoord[v+1][1] = t0; font_texcoord[v+2][0] = s1; font_texcoord[v+2][1] = t1; font_texcoord[v+3][0] = s0; font_texcoord[v+3][1] = t1; font_coord[v+0][0] = sx; font_coord[v+0][1] = sy; font_coord[v+1][0] = sx+sw; font_coord[v+1][1] = sy; font_coord[v+2][0] = sx+sw; font_coord[v+2][1] = sy+sh; font_coord[v+3][0] = sx; font_coord[v+3][1] = sy+sh; if (c->flags&CHARF_FORCEWHITE) { font_forecoloura[v+0].c = font_forecoloura[v+1].c = font_forecoloura[v+2].c = font_forecoloura[v+3].c = 0xffffffff; } else { font_forecoloura[v+0] = font_forecoloura[v+1] = font_forecoloura[v+2] = font_forecoloura[v+3] = font_forecolour; } if (font_colourmask & CON_NONCLEARBG) { sx = ((px+dxbias)*(int)vid.width) / (float)vid.rotpixelwidth; sy = ((py+dxbias)*(int)vid.height) / (float)vid.rotpixelheight; sw = sx + ((c->advance)*vid.width) / (float)vid.rotpixelwidth; sh = sy + ((font->charheight)*vid.height) / (float)vid.rotpixelheight; //don't care about texcoords font_backcoord[v+0][0] = sx; font_backcoord[v+0][1] = sy; font_backcoord[v+1][0] = sw; font_backcoord[v+1][1] = sy; font_backcoord[v+2][0] = sw; font_backcoord[v+2][1] = sh; font_backcoord[v+3][0] = sx; font_backcoord[v+3][1] = sh; font_backcoloura[v+0] = font_backcolour; font_backcoloura[v+1] = font_backcolour; font_backcoloura[v+2] = font_backcolour; font_backcoloura[v+3] = font_backcolour; } return nextx; } /*there is no sane way to make this pixel-correct*/ float Font_DrawScaleChar(float px, float py, unsigned int charflags, unsigned int codepoint) { struct charcache_s *c; float s0, s1; float t0, t1; float nextx; float sx, sy, sw, sh; int col; int v; struct font_s *font = curfont; float cw, ch; #ifdef D3D11QUAKE float dxbias = 0;//(qrenderer == QR_DIRECT3D11)?0.5:0; #else #define dxbias 0 #endif // if (!curfont_scaled) // return Font_DrawChar(px, py, charcode); if (charflags & CON_2NDCHARSETTEXT) { if (font->alt) { font = font->alt; charflags &= ~CON_2NDCHARSETTEXT; } else if (codepoint >= 0xe000 && codepoint <= 0xe0ff) charflags &= ~CON_2NDCHARSETTEXT; //don't double-dip } cw = curfont_scale[0]; ch = curfont_scale[1]; //crash if there is no current font. c = Font_GetChar(font, codepoint); if (!c) return px; nextx = px + c->advance*cw; if (codepoint == ' ' && (charflags & (CON_RICHFORECOLOUR|CON_NONCLEARBG)) != CON_NONCLEARBG) return nextx; if (charflags & CON_BLINKTEXT) { if (!cl_noblink.ival) if ((int)(realtime*3) & 1) return nextx; } if (charflags & CON_RICHFORECOLOUR) { col = charflags & (CON_2NDCHARSETTEXT|CON_RICHFORECOLOUR|(0xfff<>CON_RICHRSHIFT)&0xf)*0x11; rgba[1] = ((col>>CON_RICHGSHIFT)&0xf)*0x11; rgba[2] = ((col>>CON_RICHBSHIFT)&0xf)*0x11; rgba[3] = 255; font_backcolour.c = 0; if (charflags & CON_2NDCHARSETTEXT) { rgba[0] *= font->alttint[0]; rgba[1] *= font->alttint[1]; rgba[2] *= font->alttint[2]; } else { rgba[0] *= font->tint[0]; rgba[1] *= font->tint[1]; rgba[2] *= font->tint[2]; } rgba[0] *= font_foretint[0]; rgba[1] *= font_foretint[1]; rgba[2] *= font_foretint[2]; rgba[3] *= font_foretint[3]; font_forecolour.rgba[0] = min(rgba[0], 255); font_forecolour.rgba[1] = min(rgba[1], 255); font_forecolour.rgba[2] = min(rgba[2], 255); font_forecolour.rgba[3] = min(rgba[3], 255); } } else { col = charflags & (CON_2NDCHARSETTEXT|CON_NONCLEARBG|CON_BGMASK|CON_FGMASK|CON_HALFALPHA); if (col != font_colourmask) { vec4_t rgba; if (font_backcolour.rgba[3] != ((charflags & CON_NONCLEARBG)?127:0)) { Font_Flush(); R2D_Flush = Font_Flush; } font_colourmask = col; col = (charflags&CON_FGMASK)>>CON_FGSHIFT; if (charflags & CON_HALFALPHA) { rgba[0] = consolecolours[col].fr*0x7f; rgba[1] = consolecolours[col].fg*0x7f; rgba[2] = consolecolours[col].fb*0x7f; rgba[3] = 0x7f; } else { rgba[0] = consolecolours[col].fr*255; rgba[1] = consolecolours[col].fg*255; rgba[2] = consolecolours[col].fb*255; rgba[3] = 255; } col = (charflags&CON_BGMASK)>>CON_BGSHIFT; if (charflags & CON_NONCLEARBG) { font_backcolour.rgba[0] = consolecolours[col].fr*0xc0; font_backcolour.rgba[1] = consolecolours[col].fg*0xc0; font_backcolour.rgba[2] = consolecolours[col].fb*0xc0; font_backcolour.rgba[3] = 0xc0; } else font_backcolour.c = 0; if (charflags & CON_2NDCHARSETTEXT) { rgba[0] *= font->alttint[0]; rgba[1] *= font->alttint[1]; rgba[2] *= font->alttint[2]; } else { rgba[0] *= font->tint[0]; rgba[1] *= font->tint[1]; rgba[2] *= font->tint[2]; } rgba[0] *= font_foretint[0]; rgba[1] *= font_foretint[1]; rgba[2] *= font_foretint[2]; rgba[3] *= font_foretint[3]; font_forecolour.rgba[0] = min(rgba[0], 255); font_forecolour.rgba[1] = min(rgba[1], 255); font_forecolour.rgba[2] = min(rgba[2], 255); font_forecolour.rgba[3] = min(rgba[3], 255); } } s0 = (float)c->bmx/PLANEWIDTH; t0 = (float)c->bmy/PLANEWIDTH; s1 = (float)(c->bmx+c->bmw)/PLANEWIDTH; t1 = (float)(c->bmy+c->bmh)/PLANEWIDTH; if (c->texplane >= DEFAULTPLANE) { sx = ((px+c->left*cw)); sy = ((py+c->top*ch)); sw = ((font->charheight*cw)); sh = ((font->charheight*ch)); if (c->texplane == DEFAULTPLANE) v = Font_BeginChar(fontplanes.defaultfont); else v = Font_BeginChar(font->singletexture); } else { sx = (px+c->left*cw); sy = (py+c->top*ch); sw = ((c->bmw*cw)); sh = ((c->bmh*ch)); v = Font_BeginChar(fontplanes.texnum[c->texplane]); } sx += dxbias; sy += dxbias; sx *= (int)vid.width / (float)vid.rotpixelwidth; sy *= (int)vid.height / (float)vid.rotpixelheight; sw *= (int)vid.width / (float)vid.rotpixelwidth; sh *= (int)vid.height / (float)vid.rotpixelheight; font_texcoord[v+0][0] = s0; font_texcoord[v+0][1] = t0; font_texcoord[v+1][0] = s1; font_texcoord[v+1][1] = t0; font_texcoord[v+2][0] = s1; font_texcoord[v+2][1] = t1; font_texcoord[v+3][0] = s0; font_texcoord[v+3][1] = t1; font_coord[v+0][0] = sx; font_coord[v+0][1] = sy; font_coord[v+1][0] = sx+sw; font_coord[v+1][1] = sy; font_coord[v+2][0] = sx+sw; font_coord[v+2][1] = sy+sh; font_coord[v+3][0] = sx; font_coord[v+3][1] = sy+sh; font_forecoloura[v+0] = font_forecolour; font_forecoloura[v+1] = font_forecolour; font_forecoloura[v+2] = font_forecolour; font_forecoloura[v+3] = font_forecolour; if (font_colourmask & CON_NONCLEARBG) { sx = px + dxbias; sy = py + dxbias; sw = sx + c->advance; sh = sy + font->charheight; sx *= (int)vid.width / (float)vid.rotpixelwidth; sy *= (int)vid.height / (float)vid.rotpixelheight; sw *= (int)vid.width / (float)vid.rotpixelwidth; sh *= (int)vid.height / (float)vid.rotpixelheight; //don't care about texcoords font_backcoord[v+0][0] = sx; font_backcoord[v+0][1] = sy; font_backcoord[v+1][0] = sw; font_backcoord[v+1][1] = sy; font_backcoord[v+2][0] = sw; font_backcoord[v+2][1] = sh; font_backcoord[v+3][0] = sx; font_backcoord[v+3][1] = sh; font_backcoloura[v+0] = font_backcolour; font_backcoloura[v+1] = font_backcolour; font_backcoloura[v+2] = font_backcolour; font_backcoloura[v+3] = font_backcolour; } return nextx; } #endif //!SERVERONLY