2 Copyright (C) 1996-1997 Id Software, Inc.
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
28 #include "ft2_fontdefs.h"
34 // this flag indicates that it should be loaded and unloaded on demand
36 // texture flags to upload with
38 // texture may be freed after a while
41 skinframe_t *skinframe;
42 // used for hash lookups
43 struct cachepic_s *chain;
44 // flags - CACHEPICFLAG_NEWPIC for example
51 static mempool_t *fonts_mempool = NULL;
53 cvar_t r_textshadow = {CVAR_CLIENT | CVAR_SAVE, "r_textshadow", "0", "draws a shadow on all text to improve readability (note: value controls offset, 1 = 1 pixel, 1.5 = 1.5 pixels, etc)"};
54 cvar_t r_textbrightness = {CVAR_CLIENT | CVAR_SAVE, "r_textbrightness", "0", "additional brightness for text color codes (0 keeps colors as is, 1 makes them all white)"};
55 cvar_t r_textcontrast = {CVAR_CLIENT | CVAR_SAVE, "r_textcontrast", "1", "additional contrast for text color codes (1 keeps colors as is, 0 makes them all black)"};
57 cvar_t r_font_postprocess_blur = {CVAR_CLIENT | CVAR_SAVE, "r_font_postprocess_blur", "0", "font blur amount"};
58 cvar_t r_font_postprocess_outline = {CVAR_CLIENT | CVAR_SAVE, "r_font_postprocess_outline", "0", "font outline amount"};
59 cvar_t r_font_postprocess_shadow_x = {CVAR_CLIENT | CVAR_SAVE, "r_font_postprocess_shadow_x", "0", "font shadow X shift amount, applied during outlining"};
60 cvar_t r_font_postprocess_shadow_y = {CVAR_CLIENT | CVAR_SAVE, "r_font_postprocess_shadow_y", "0", "font shadow Y shift amount, applied during outlining"};
61 cvar_t r_font_postprocess_shadow_z = {CVAR_CLIENT | CVAR_SAVE, "r_font_postprocess_shadow_z", "0", "font shadow Z shift amount, applied during blurring"};
62 cvar_t r_font_hinting = {CVAR_CLIENT | CVAR_SAVE, "r_font_hinting", "3", "0 = no hinting, 1 = light autohinting, 2 = full autohinting, 3 = full hinting"};
63 cvar_t r_font_antialias = {CVAR_CLIENT | CVAR_SAVE, "r_font_antialias", "1", "0 = monochrome, 1 = grey" /* , 2 = rgb, 3 = bgr" */};
64 cvar_t r_nearest_2d = {CVAR_CLIENT | CVAR_SAVE, "r_nearest_2d", "0", "use nearest filtering on all 2d textures (including conchars)"};
65 cvar_t r_nearest_conchars = {CVAR_CLIENT | CVAR_SAVE, "r_nearest_conchars", "0", "use nearest filtering on conchars texture"};
67 //=============================================================================
68 /* Support Routines */
70 static cachepic_t *cachepichash[CACHEPICHASHSIZE];
71 static cachepic_t cachepics[MAX_CACHED_PICS];
72 static int numcachepics;
74 rtexturepool_t *drawtexturepool;
83 // FIXME: move this to client somehow
84 cachepic_t *Draw_CachePic_Flags(const char *path, unsigned int cachepicflags)
90 texflags = TEXF_ALPHA;
91 if (!(cachepicflags & CACHEPICFLAG_NOCLAMP))
92 texflags |= TEXF_CLAMP;
93 if (cachepicflags & CACHEPICFLAG_MIPMAP)
94 texflags |= TEXF_MIPMAP;
95 if (!(cachepicflags & CACHEPICFLAG_NOCOMPRESSION) && gl_texturecompression_2d.integer && gl_texturecompression.integer)
96 texflags |= TEXF_COMPRESS;
97 if (cachepicflags & CACHEPICFLAG_LINEAR)
98 texflags |= TEXF_FORCELINEAR;
99 else if ((cachepicflags & CACHEPICFLAG_NEAREST) || r_nearest_2d.integer)
100 texflags |= TEXF_FORCENEAREST;
102 // check whether the picture has already been cached
103 crc = CRC_Block((unsigned char *)path, strlen(path));
104 hashkey = ((crc >> 8) ^ crc) % CACHEPICHASHSIZE;
105 for (pic = cachepichash[hashkey];pic;pic = pic->chain)
107 if (!strcmp(path, pic->name))
109 // if it was created (or replaced) by Draw_NewPic, just return it
110 if (!(pic->flags & CACHEPICFLAG_NEWPIC))
112 // reload the pic if texflags changed in important ways
113 // ignore TEXF_COMPRESS when comparing, because fallback pics remove the flag, and ignore TEXF_MIPMAP because QC specifies that
114 if ((pic->texflags ^ texflags) & ~(TEXF_COMPRESS | TEXF_MIPMAP))
116 Con_DPrintf("Draw_CachePic(\"%s\"): frame %i: reloading pic due to mismatch on flags\n", path, draw_frame);
119 if (!pic->skinframe || !pic->skinframe->base)
121 if (pic->flags & CACHEPICFLAG_FAILONMISSING)
123 Con_DPrintf("Draw_CachePic(\"%s\"): frame %i: reloading pic\n", path, draw_frame);
126 if (!(cachepicflags & CACHEPICFLAG_NOTPERSISTENT))
127 pic->autoload = false; // caller is making this pic persistent
130 R_SkinFrame_MarkUsed(pic->skinframe);
131 pic->lastusedframe = draw_frame;
136 if (numcachepics == MAX_CACHED_PICS)
138 Con_DPrintf ("Draw_CachePic(\"%s\"): frame %i: numcachepics == MAX_CACHED_PICS\n", path, draw_frame);
139 // FIXME: support NULL in callers?
140 return cachepics; // return the first one
142 Con_DPrintf("Draw_CachePic(\"%s\"): frame %i: loading pic%s\n", path, draw_frame, (cachepicflags & CACHEPICFLAG_NOTPERSISTENT) ? " notpersist" : "");
143 pic = cachepics + (numcachepics++);
144 memset(pic, 0, sizeof(*pic));
145 strlcpy (pic->name, path, sizeof(pic->name));
147 pic->chain = cachepichash[hashkey];
148 cachepichash[hashkey] = pic;
152 R_SkinFrame_PurgeSkinFrame(pic->skinframe);
154 pic->flags = cachepicflags;
155 pic->texflags = texflags;
156 pic->autoload = (cachepicflags & CACHEPICFLAG_NOTPERSISTENT) != 0;
157 pic->lastusedframe = draw_frame;
161 // reload image after it was unloaded or texflags changed significantly
162 R_SkinFrame_LoadExternal_SkinFrame(pic->skinframe, pic->name, texflags | TEXF_FORCE_RELOAD, (cachepicflags & CACHEPICFLAG_QUIET) == 0, (cachepicflags & CACHEPICFLAG_FAILONMISSING) == 0);
166 // load high quality image (this falls back to low quality too)
167 pic->skinframe = R_SkinFrame_LoadExternal(pic->name, texflags | TEXF_FORCE_RELOAD, (cachepicflags & CACHEPICFLAG_QUIET) == 0, (cachepicflags & CACHEPICFLAG_FAILONMISSING) == 0);
170 // get the dimensions of the image we loaded (if it was successful)
171 if (pic->skinframe && pic->skinframe->base)
173 pic->width = R_TextureWidth(pic->skinframe->base);
174 pic->height = R_TextureHeight(pic->skinframe->base);
177 // check for a low quality version of the pic and use its size if possible, to match the stock hud
178 Image_GetStockPicSize(pic->name, &pic->width, &pic->height);
183 cachepic_t *Draw_CachePic (const char *path)
185 return Draw_CachePic_Flags (path, 0); // default to persistent!
188 const char *Draw_GetPicName(cachepic_t *pic)
195 int Draw_GetPicWidth(cachepic_t *pic)
202 int Draw_GetPicHeight(cachepic_t *pic)
209 qboolean Draw_IsPicLoaded(cachepic_t *pic)
213 if (pic->autoload && (!pic->skinframe || !pic->skinframe->base))
215 Con_DPrintf("Draw_IsPicLoaded(\"%s\"): Loading external skin\n", pic->name);
216 pic->skinframe = R_SkinFrame_LoadExternal(pic->name, pic->texflags | TEXF_FORCE_RELOAD, false, true);
218 // skinframe will only be NULL if the pic was created with CACHEPICFLAG_FAILONMISSING and not found
219 return pic->skinframe != NULL && pic->skinframe->base != NULL;
222 rtexture_t *Draw_GetPicTexture(cachepic_t *pic)
226 if (pic->autoload && (!pic->skinframe || !pic->skinframe->base))
228 Con_DPrintf("Draw_GetPicTexture(\"%s\"): Loading external skin\n", pic->name);
229 pic->skinframe = R_SkinFrame_LoadExternal(pic->name, pic->texflags | TEXF_FORCE_RELOAD, false, true);
231 pic->lastusedframe = draw_frame;
232 return pic->skinframe ? pic->skinframe->base : NULL;
235 void Draw_Frame(void)
239 static double nextpurgetime;
240 if (nextpurgetime > host.realtime)
242 nextpurgetime = host.realtime + 0.05;
243 for (i = 0, pic = cachepics;i < numcachepics;i++, pic++)
245 if (pic->autoload && pic->skinframe && pic->skinframe->base && pic->lastusedframe < draw_frame - 3)
247 Con_DPrintf("Draw_Frame(%i): Unloading \"%s\"\n", draw_frame, pic->name);
248 R_SkinFrame_PurgeSkinFrame(pic->skinframe);
254 cachepic_t *Draw_NewPic(const char *picname, int width, int height, unsigned char *pixels_bgra, textype_t textype, int texflags)
259 crc = CRC_Block((unsigned char *)picname, strlen(picname));
260 hashkey = ((crc >> 8) ^ crc) % CACHEPICHASHSIZE;
261 for (pic = cachepichash[hashkey];pic;pic = pic->chain)
262 if (!strcmp (picname, pic->name))
267 if (pic->flags & CACHEPICFLAG_NEWPIC && pic->skinframe && pic->skinframe->base && pic->width == width && pic->height == height)
269 Con_DPrintf("Draw_NewPic(\"%s\"): frame %i: updating texture\n", picname, draw_frame);
270 R_UpdateTexture(pic->skinframe->base, pixels_bgra, 0, 0, 0, width, height, 1);
271 R_SkinFrame_MarkUsed(pic->skinframe);
272 pic->lastusedframe = draw_frame;
275 Con_DPrintf("Draw_NewPic(\"%s\"): frame %i: reloading pic because flags/size changed\n", picname, draw_frame);
279 if (numcachepics == MAX_CACHED_PICS)
281 Con_DPrintf ("Draw_NewPic(\"%s\"): frame %i: numcachepics == MAX_CACHED_PICS\n", picname, draw_frame);
282 // FIXME: support NULL in callers?
283 return cachepics; // return the first one
285 Con_DPrintf("Draw_NewPic(\"%s\"): frame %i: creating new cachepic\n", picname, draw_frame);
286 pic = cachepics + (numcachepics++);
287 memset(pic, 0, sizeof(*pic));
288 strlcpy (pic->name, picname, sizeof(pic->name));
290 pic->chain = cachepichash[hashkey];
291 cachepichash[hashkey] = pic;
294 R_SkinFrame_PurgeSkinFrame(pic->skinframe);
296 pic->autoload = false;
297 pic->flags = CACHEPICFLAG_NEWPIC; // disable texflags checks in Draw_CachePic
298 pic->flags |= (texflags & TEXF_CLAMP) ? 0 : CACHEPICFLAG_NOCLAMP;
299 pic->flags |= (texflags & TEXF_FORCENEAREST) ? CACHEPICFLAG_NEAREST : 0;
301 pic->height = height;
302 pic->skinframe = R_SkinFrame_LoadInternalBGRA(picname, texflags | TEXF_FORCE_RELOAD, pixels_bgra, width, height, 0, 0, 0, vid.sRGB2D);
303 pic->lastusedframe = draw_frame;
307 void Draw_FreePic(const char *picname)
312 // this doesn't really free the pic, but does free its texture
313 crc = CRC_Block((unsigned char *)picname, strlen(picname));
314 hashkey = ((crc >> 8) ^ crc) % CACHEPICHASHSIZE;
315 for (pic = cachepichash[hashkey];pic;pic = pic->chain)
317 if (!strcmp (picname, pic->name) && pic->skinframe)
319 Con_DPrintf("Draw_FreePic(\"%s\"): frame %i: freeing pic\n", picname, draw_frame);
320 R_SkinFrame_PurgeSkinFrame(pic->skinframe);
326 qboolean Draw_PicExists(const char *name) {
327 char vabuf[1024] = { 0 };
328 const char *checkfmt[] = { "%s.tga", "%s.png", "%s.jpg", "%s.pcx" };
330 // TODO: actually use the gfx format list for this
331 for (i = 0; i < sizeof(checkfmt) / sizeof(checkfmt[0]); ++i)
332 if (FS_FileExists(va(vabuf, sizeof(vabuf), checkfmt[i], name)))
337 static float snap_to_pixel_x(float x, float roundUpAt);
338 extern int con_linewidth; // to force rewrapping
339 void LoadFont(qboolean override, const char *name, dp_font_t *fnt, float scale, float voffset)
343 char widthfile[MAX_QPATH];
345 fs_offset_t widthbufsize;
347 if(override || !fnt->texpath[0])
349 strlcpy(fnt->texpath, name, sizeof(fnt->texpath));
350 // load the cvars when the font is FIRST loader
351 fnt->settings.scale = scale;
352 fnt->settings.voffset = voffset;
353 fnt->settings.antialias = r_font_antialias.integer;
354 fnt->settings.hinting = r_font_hinting.integer;
355 fnt->settings.outline = r_font_postprocess_outline.value;
356 fnt->settings.blur = r_font_postprocess_blur.value;
357 fnt->settings.shadowx = r_font_postprocess_shadow_x.value;
358 fnt->settings.shadowy = r_font_postprocess_shadow_y.value;
359 fnt->settings.shadowz = r_font_postprocess_shadow_z.value;
362 if (fnt->settings.scale <= 0)
363 fnt->settings.scale = 1;
365 if(drawtexturepool == NULL)
366 return; // before gl_draw_start, so will be loaded later
370 // clear freetype font
371 Font_UnloadFont(fnt->ft2);
376 if(fnt->req_face != -1)
378 if(!Font_LoadFont(fnt->texpath, fnt))
379 Con_DPrintf("Failed to load font-file for '%s', it will not support as many characters.\n", fnt->texpath);
382 fnt->pic = Draw_CachePic_Flags(fnt->texpath, CACHEPICFLAG_QUIET | CACHEPICFLAG_NOCOMPRESSION | (r_nearest_conchars.integer ? CACHEPICFLAG_NEAREST : 0) | CACHEPICFLAG_FAILONMISSING);
383 if(!Draw_IsPicLoaded(fnt->pic))
385 for (i = 0; i < MAX_FONT_FALLBACKS; ++i)
387 if (!fnt->fallbacks[i][0])
389 fnt->pic = Draw_CachePic_Flags(fnt->fallbacks[i], CACHEPICFLAG_QUIET | CACHEPICFLAG_NOCOMPRESSION | (r_nearest_conchars.integer ? CACHEPICFLAG_NEAREST : 0) | CACHEPICFLAG_FAILONMISSING);
390 if(Draw_IsPicLoaded(fnt->pic))
393 if(!Draw_IsPicLoaded(fnt->pic))
395 fnt->pic = Draw_CachePic_Flags("gfx/conchars", CACHEPICFLAG_NOCOMPRESSION | (r_nearest_conchars.integer ? CACHEPICFLAG_NEAREST : 0));
396 strlcpy(widthfile, "gfx/conchars.width", sizeof(widthfile));
399 dpsnprintf(widthfile, sizeof(widthfile), "%s.width", fnt->fallbacks[i]);
402 dpsnprintf(widthfile, sizeof(widthfile), "%s.width", fnt->texpath);
404 // unspecified width == 1 (base width)
405 for(ch = 0; ch < 256; ++ch)
406 fnt->width_of[ch] = 1;
408 // FIXME load "name.width", if it fails, fill all with 1
409 if((widthbuf = (char *) FS_LoadFile(widthfile, tempmempool, true, &widthbufsize)))
411 float extraspacing = 0;
412 const char *p = widthbuf;
417 if(!COM_ParseToken_Simple(&p, false, false, true))
435 fnt->width_of[ch] = atof(com_token) + extraspacing;
439 if(!strcmp(com_token, "extraspacing"))
441 if(!COM_ParseToken_Simple(&p, false, false, true))
443 extraspacing = atof(com_token);
445 else if(!strcmp(com_token, "scale"))
447 if(!COM_ParseToken_Simple(&p, false, false, true))
449 fnt->settings.scale = atof(com_token);
453 Con_DPrintf("Warning: skipped unknown font property %s\n", com_token);
454 if(!COM_ParseToken_Simple(&p, false, false, true))
466 for (i = 0; i < MAX_FONT_SIZES; ++i)
468 ft2_font_map_t *map = Font_MapForIndex(fnt->ft2, i);
471 for(ch = 0; ch < 256; ++ch)
472 map->width_of[ch] = Font_SnapTo(fnt->width_of[ch], 1/map->size);
476 maxwidth = fnt->width_of[0];
477 for(i = 1; i < 256; ++i)
478 maxwidth = max(maxwidth, fnt->width_of[i]);
479 fnt->maxwidth = maxwidth;
481 // fix up maxwidth for overlap
482 fnt->maxwidth *= fnt->settings.scale;
484 if(fnt == FONT_CONSOLE)
485 con_linewidth = -1; // rewrap console in next frame
488 extern cvar_t developer_font;
489 dp_font_t *FindFont(const char *title, qboolean allocate_new)
494 for(i = 0; i < dp_fonts.maxsize; ++i)
495 if(!strcmp(dp_fonts.f[i].title, title))
496 return &dp_fonts.f[i];
497 // if not found - try allocate
500 // find any font with empty title
501 for(i = 0; i < dp_fonts.maxsize; ++i)
503 if(!strcmp(dp_fonts.f[i].title, ""))
505 strlcpy(dp_fonts.f[i].title, title, sizeof(dp_fonts.f[i].title));
506 return &dp_fonts.f[i];
509 // if no any 'free' fonts - expand buffer
510 oldsize = dp_fonts.maxsize;
511 dp_fonts.maxsize = dp_fonts.maxsize + FONTS_EXPAND;
512 if (developer_font.integer)
513 Con_Printf("FindFont: enlarging fonts buffer (%i -> %i)\n", oldsize, dp_fonts.maxsize);
514 dp_fonts.f = (dp_font_t *)Mem_Realloc(fonts_mempool, dp_fonts.f, sizeof(dp_font_t) * dp_fonts.maxsize);
515 // relink ft2 structures
516 for(i = 0; i < oldsize; ++i)
517 if (dp_fonts.f[i].ft2)
518 dp_fonts.f[i].ft2->settings = &dp_fonts.f[i].settings;
519 // register a font in first expanded slot
520 strlcpy(dp_fonts.f[oldsize].title, title, sizeof(dp_fonts.f[oldsize].title));
521 return &dp_fonts.f[oldsize];
526 static float snap_to_pixel_x(float x, float roundUpAt)
528 float pixelpos = x * vid.width / vid_conwidth.value;
529 int snap = (int) pixelpos;
530 if (pixelpos - snap >= roundUpAt) ++snap;
531 return ((float)snap * vid_conwidth.value / vid.width);
533 x = (int)(x * vid.width / vid_conwidth.value);
534 x = (x * vid_conwidth.value / vid.width);
539 static float snap_to_pixel_y(float y, float roundUpAt)
541 float pixelpos = y * vid.height / vid_conheight.value;
542 int snap = (int) pixelpos;
543 if (pixelpos - snap > roundUpAt) ++snap;
544 return ((float)snap * vid_conheight.value / vid.height);
546 y = (int)(y * vid.height / vid_conheight.value);
547 y = (y * vid_conheight.value / vid.height);
552 static void LoadFont_f(cmd_state_t *cmd)
556 const char *filelist, *c, *cm;
557 float sz, scale, voffset;
558 char mainfont[MAX_QPATH];
560 if(Cmd_Argc(cmd) < 2)
562 Con_Printf("Available font commands:\n");
563 for(i = 0; i < dp_fonts.maxsize; ++i)
564 if (dp_fonts.f[i].title[0])
565 Con_Printf(" loadfont %s gfx/tgafile[...] [custom switches] [sizes...]\n", dp_fonts.f[i].title);
566 Con_Printf("A font can simply be gfx/tgafile, or alternatively you\n"
567 "can specify multiple fonts and faces\n"
568 "Like this: gfx/vera-sans:2,gfx/fallback:1\n"
569 "to load face 2 of the font gfx/vera-sans and use face 1\n"
570 "of gfx/fallback as fallback font.\n"
571 "You can also specify a list of font sizes to load, like this:\n"
572 "loadfont console gfx/conchars,gfx/fallback 8 12 16 24 32\n"
573 "In many cases, 8 12 16 24 32 should be a good choice.\n"
575 " scale x : scale all characters by this amount when rendering (doesnt change line height)\n"
576 " voffset x : offset all chars vertical when rendering, this is multiplied to character height\n"
580 f = FindFont(Cmd_Argv(cmd, 1), true);
583 Con_Printf("font function not found\n");
587 if(Cmd_Argc(cmd) < 3)
588 filelist = "gfx/conchars";
590 filelist = Cmd_Argv(cmd, 2);
592 memset(f->fallbacks, 0, sizeof(f->fallbacks));
593 memset(f->fallback_faces, 0, sizeof(f->fallback_faces));
595 // first font is handled "normally"
596 c = strchr(filelist, ':');
597 cm = strchr(filelist, ',');
598 if(c && (!cm || c < cm))
599 f->req_face = atoi(c+1);
606 if(!c || (c - filelist) > MAX_QPATH)
607 strlcpy(mainfont, filelist, sizeof(mainfont));
610 memcpy(mainfont, filelist, c - filelist);
611 mainfont[c - filelist] = 0;
614 for(i = 0; i < MAX_FONT_FALLBACKS; ++i)
616 c = strchr(filelist, ',');
622 c = strchr(filelist, ':');
623 cm = strchr(filelist, ',');
624 if(c && (!cm || c < cm))
625 f->fallback_faces[i] = atoi(c+1);
628 f->fallback_faces[i] = 0; // f->req_face; could make it stick to the default-font's face index
631 if(!c || (c-filelist) > MAX_QPATH)
633 strlcpy(f->fallbacks[i], filelist, sizeof(mainfont));
637 memcpy(f->fallbacks[i], filelist, c - filelist);
638 f->fallbacks[i][c - filelist] = 0;
642 // for now: by default load only one size: the default size
644 for(i = 1; i < MAX_FONT_SIZES; ++i)
645 f->req_sizes[i] = -1;
649 if(Cmd_Argc(cmd) >= 4)
651 for(sizes = 0, i = 3; i < Cmd_Argc(cmd); ++i)
654 if (!strcmp(Cmd_Argv(cmd, i), "scale"))
657 if (i < Cmd_Argc(cmd))
658 scale = atof(Cmd_Argv(cmd, i));
661 if (!strcmp(Cmd_Argv(cmd, i), "voffset"))
664 if (i < Cmd_Argc(cmd))
665 voffset = atof(Cmd_Argv(cmd, i));
670 continue; // no slot for other sizes
672 // parse one of sizes
673 sz = atof(Cmd_Argv(cmd, i));
674 if (sz > 0.001f && sz < 1000.0f) // do not use crap sizes
676 // search for duplicated sizes
678 for (j=0; j<sizes; j++)
679 if (f->req_sizes[j] == sz)
682 continue; // sz already in req_sizes, don't add it again
684 if (sizes == MAX_FONT_SIZES)
686 Con_Printf(CON_WARN "Warning: specified more than %i different font sizes, exceding ones are ignored\n", MAX_FONT_SIZES);
690 f->req_sizes[sizes] = sz;
696 LoadFont(true, mainfont, f, scale, voffset);
704 static void gl_draw_start(void)
708 drawtexturepool = R_AllocTexturePool();
711 memset(cachepichash, 0, sizeof(cachepichash));
715 // load default font textures
716 for(i = 0; i < dp_fonts.maxsize; ++i)
717 if (dp_fonts.f[i].title[0])
718 LoadFont(false, va(vabuf, sizeof(vabuf), "gfx/font_%s", dp_fonts.f[i].title), &dp_fonts.f[i], 1, 0);
721 static void gl_draw_shutdown(void)
725 R_FreeTexturePool(&drawtexturepool);
728 memset(cachepichash, 0, sizeof(cachepichash));
731 static void gl_draw_newmap(void)
736 // mark all of the persistent pics so they are not purged...
737 for (i = 0; i < numcachepics; i++)
739 cachepic_t *pic = cachepics + i;
740 if (!pic->autoload && pic->skinframe)
741 R_SkinFrame_MarkUsed(pic->skinframe);
745 void GL_Draw_Init (void)
749 Cvar_RegisterVariable(&r_font_postprocess_blur);
750 Cvar_RegisterVariable(&r_font_postprocess_outline);
751 Cvar_RegisterVariable(&r_font_postprocess_shadow_x);
752 Cvar_RegisterVariable(&r_font_postprocess_shadow_y);
753 Cvar_RegisterVariable(&r_font_postprocess_shadow_z);
754 Cvar_RegisterVariable(&r_font_hinting);
755 Cvar_RegisterVariable(&r_font_antialias);
756 Cvar_RegisterVariable(&r_textshadow);
757 Cvar_RegisterVariable(&r_textbrightness);
758 Cvar_RegisterVariable(&r_textcontrast);
759 Cvar_RegisterVariable(&r_nearest_2d);
760 Cvar_RegisterVariable(&r_nearest_conchars);
762 // allocate fonts storage
763 fonts_mempool = Mem_AllocPool("FONTS", 0, NULL);
764 dp_fonts.maxsize = MAX_FONTS;
765 dp_fonts.f = (dp_font_t *)Mem_Alloc(fonts_mempool, sizeof(dp_font_t) * dp_fonts.maxsize);
766 memset(dp_fonts.f, 0, sizeof(dp_font_t) * dp_fonts.maxsize);
768 // assign starting font names
769 strlcpy(FONT_DEFAULT->title, "default", sizeof(FONT_DEFAULT->title));
770 strlcpy(FONT_DEFAULT->texpath, "gfx/conchars", sizeof(FONT_DEFAULT->texpath));
771 strlcpy(FONT_CONSOLE->title, "console", sizeof(FONT_CONSOLE->title));
772 strlcpy(FONT_SBAR->title, "sbar", sizeof(FONT_SBAR->title));
773 strlcpy(FONT_NOTIFY->title, "notify", sizeof(FONT_NOTIFY->title));
774 strlcpy(FONT_CHAT->title, "chat", sizeof(FONT_CHAT->title));
775 strlcpy(FONT_CENTERPRINT->title, "centerprint", sizeof(FONT_CENTERPRINT->title));
776 strlcpy(FONT_INFOBAR->title, "infobar", sizeof(FONT_INFOBAR->title));
777 strlcpy(FONT_MENU->title, "menu", sizeof(FONT_MENU->title));
778 for(i = 0, j = 0; i < MAX_USERFONTS; ++i)
779 if(!FONT_USER(i)->title[0])
780 dpsnprintf(FONT_USER(i)->title, sizeof(FONT_USER(i)->title), "user%d", j++);
782 Cmd_AddCommand(CMD_CLIENT, "loadfont", LoadFont_f, "loadfont function tganame loads a font; example: loadfont console gfx/veramono; loadfont without arguments lists the available functions");
783 R_RegisterModule("GL_Draw", gl_draw_start, gl_draw_shutdown, gl_draw_newmap, NULL, NULL);
786 void DrawQ_Start(void)
788 r_refdef.draw2dstage = 1;
789 R_ResetViewRendering2D_Common(0, NULL, NULL, 0, 0, vid.width, vid.height, vid_conwidth.integer, vid_conheight.integer);
792 qboolean r_draw2d_force = false;
794 void DrawQ_Pic(float x, float y, cachepic_t *pic, float width, float height, float red, float green, float blue, float alpha, int flags)
796 dp_model_t *mod = CL_Mesh_UI();
800 pic = Draw_CachePic("white");
801 // make sure pic is loaded - we don't use the texture here, Mod_Mesh_GetTexture looks up the skinframe by name
802 Draw_GetPicTexture(pic);
806 height = pic->height;
807 surf = Mod_Mesh_AddSurface(mod, Mod_Mesh_GetTexture(mod, pic->name, flags, pic->texflags, MATERIALFLAG_WALL | MATERIALFLAG_VERTEXCOLOR | MATERIALFLAG_ALPHAGEN_VERTEX | MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW), true);
808 e0 = Mod_Mesh_IndexForVertex(mod, surf, x , y , 0, 0, 0, -1, 0, 0, 0, 0, red, green, blue, alpha);
809 e1 = Mod_Mesh_IndexForVertex(mod, surf, x + width, y , 0, 0, 0, -1, 1, 0, 0, 0, red, green, blue, alpha);
810 e2 = Mod_Mesh_IndexForVertex(mod, surf, x + width, y + height, 0, 0, 0, -1, 1, 1, 0, 0, red, green, blue, alpha);
811 e3 = Mod_Mesh_IndexForVertex(mod, surf, x , y + height, 0, 0, 0, -1, 0, 1, 0, 0, red, green, blue, alpha);
812 Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
813 Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
816 void DrawQ_RotPic(float x, float y, cachepic_t *pic, float width, float height, float org_x, float org_y, float angle, float red, float green, float blue, float alpha, int flags)
818 float af = DEG2RAD(-angle); // forward
819 float ar = DEG2RAD(-angle + 90); // right
820 float sinaf = sin(af);
821 float cosaf = cos(af);
822 float sinar = sin(ar);
823 float cosar = cos(ar);
824 dp_model_t *mod = CL_Mesh_UI();
828 pic = Draw_CachePic("white");
829 // make sure pic is loaded - we don't use the texture here, Mod_Mesh_GetTexture looks up the skinframe by name
830 Draw_GetPicTexture(pic);
834 height = pic->height;
835 surf = Mod_Mesh_AddSurface(mod, Mod_Mesh_GetTexture(mod, pic->name, flags, pic->texflags, MATERIALFLAG_WALL | MATERIALFLAG_VERTEXCOLOR | MATERIALFLAG_ALPHAGEN_VERTEX | MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW), true);
836 e0 = Mod_Mesh_IndexForVertex(mod, surf, x - cosaf * org_x - cosar * org_y , y - sinaf * org_x - sinar * org_y , 0, 0, 0, -1, 0, 0, 0, 0, red, green, blue, alpha);
837 e1 = Mod_Mesh_IndexForVertex(mod, surf, x + cosaf * (width - org_x) - cosar * org_y , y + sinaf * (width - org_x) - sinar * org_y , 0, 0, 0, -1, 1, 0, 0, 0, red, green, blue, alpha);
838 e2 = Mod_Mesh_IndexForVertex(mod, surf, x + cosaf * (width - org_x) + cosar * (height - org_y), y + sinaf * (width - org_x) + sinar * (height - org_y), 0, 0, 0, -1, 1, 1, 0, 0, red, green, blue, alpha);
839 e3 = Mod_Mesh_IndexForVertex(mod, surf, x - cosaf * org_x + cosar * (height - org_y), y - sinaf * org_x + sinar * (height - org_y), 0, 0, 0, -1, 0, 1, 0, 0, red, green, blue, alpha);
840 Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
841 Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
844 void DrawQ_Fill(float x, float y, float width, float height, float red, float green, float blue, float alpha, int flags)
846 DrawQ_Pic(x, y, Draw_CachePic("white"), width, height, red, green, blue, alpha, flags);
849 /// color tag printing
850 static const vec4_t string_colors[] =
853 // LadyHavoc: why on earth is cyan before magenta in Quake3?
854 // LadyHavoc: note: Doom3 uses white for [0] and [7]
855 {0.0, 0.0, 0.0, 1.0}, // black
856 {1.0, 0.0, 0.0, 1.0}, // red
857 {0.0, 1.0, 0.0, 1.0}, // green
858 {1.0, 1.0, 0.0, 1.0}, // yellow
859 {0.0, 0.0, 1.0, 1.0}, // blue
860 {0.0, 1.0, 1.0, 1.0}, // cyan
861 {1.0, 0.0, 1.0, 1.0}, // magenta
862 {1.0, 1.0, 1.0, 1.0}, // white
863 // [515]'s BX_COLOREDTEXT extension
864 {1.0, 1.0, 1.0, 0.5}, // half transparent
865 {0.5, 0.5, 0.5, 1.0} // half brightness
866 // Black's color table
867 //{1.0, 1.0, 1.0, 1.0},
868 //{1.0, 0.0, 0.0, 1.0},
869 //{0.0, 1.0, 0.0, 1.0},
870 //{0.0, 0.0, 1.0, 1.0},
871 //{1.0, 1.0, 0.0, 1.0},
872 //{0.0, 1.0, 1.0, 1.0},
873 //{1.0, 0.0, 1.0, 1.0},
874 //{0.1, 0.1, 0.1, 1.0}
877 #define STRING_COLORS_COUNT (sizeof(string_colors) / sizeof(vec4_t))
879 static void DrawQ_GetTextColor(float color[4], int colorindex, float r, float g, float b, float a, qboolean shadow)
881 float C = r_textcontrast.value;
882 float B = r_textbrightness.value;
883 if (colorindex & 0x10000) // that bit means RGB color
885 color[0] = ((colorindex >> 12) & 0xf) / 15.0;
886 color[1] = ((colorindex >> 8) & 0xf) / 15.0;
887 color[2] = ((colorindex >> 4) & 0xf) / 15.0;
888 color[3] = (colorindex & 0xf) / 15.0;
891 Vector4Copy(string_colors[colorindex], color);
892 Vector4Set(color, color[0] * r * C + B, color[1] * g * C + B, color[2] * b * C + B, color[3] * a);
895 float shadowalpha = (color[0]+color[1]+color[2]) * 0.8;
896 Vector4Set(color, 0, 0, 0, color[3] * bound(0, shadowalpha, 1));
900 // returns a colorindex (format 0x1RGBA) if str is a valid RGB string
901 // returns 0 otherwise
902 static int RGBstring_to_colorindex(const char *str)
905 int ind = 0x0001 << 4;
907 if (*str <= '9' && *str >= '0')
912 if (ch >= 'a' && ch <= 'f')
919 } while(!(ind & 0x10000));
920 return ind | 0xf; // add costant alpha value
923 // NOTE: this function always draws exactly one character if maxwidth <= 0
924 float DrawQ_TextWidth_UntilWidth_TrackColors_Scale(const char *text, size_t *maxlen, float w, float h, float sw, float sh, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxwidth)
926 const char *text_start = text;
930 Uchar ch, mapch, nextch;
931 Uchar prevch = 0; // used for kerning
935 ft2_font_map_t *fontmap = NULL;
936 ft2_font_map_t *map = NULL;
937 //ft2_font_map_t *prevmap = NULL;
938 ft2_font_t *ft2 = fnt->ft2;
940 qboolean snap = true;
941 qboolean least_one = false;
942 float dw; // display w
943 //float dh; // display h
944 const float *width_of;
951 // do this in the end
952 w *= fnt->settings.scale;
953 h *= fnt->settings.scale;
955 // find the most fitting size:
959 map_index = Font_IndexForSize(ft2, h, &w, &h);
961 map_index = Font_IndexForSize(ft2, h, NULL, NULL);
962 fontmap = Font_MapForIndex(ft2, map_index);
971 if (!outcolor || *outcolor == -1)
972 colorindex = STRING_COLOR_DEFAULT;
974 colorindex = *outcolor;
976 // maxwidth /= fnt->scale; // w and h are multiplied by it already
977 // ftbase_x = snap_to_pixel_x(0);
982 maxwidth = -maxwidth;
986 // x = snap_to_pixel_x(x, 0.4); // haha, it's 0 anyway
989 width_of = fontmap->width_of;
991 width_of = fnt->width_of;
994 while (((bytes_left = *maxlen - (text - text_start)) > 0) && *text)
997 nextch = ch = u8_getnchar(text, &text, bytes_left);
998 i = text - text_start;
1001 if (ch == ' ' && !fontmap)
1003 if(!least_one || i0) // never skip the first character
1004 if(x + width_of[(int) ' '] * dw > maxwidth)
1007 break; // oops, can't draw this
1009 x += width_of[(int) ' '] * dw;
1012 if (ch == STRING_COLOR_TAG && !ignorecolorcodes && i < *maxlen)
1014 ch = *text; // colors are ascii, so no u8_ needed
1015 if (ch <= '9' && ch >= '0') // ^[0-9] found
1017 colorindex = ch - '0';
1022 else if (ch == STRING_COLOR_RGB_TAG_CHAR && i + 3 < *maxlen ) // ^x found
1024 const char *text_p = &text[1];
1025 int tempcolorindex = RGBstring_to_colorindex(text_p);
1028 colorindex = tempcolorindex;
1034 else if (ch == STRING_COLOR_TAG) // ^^ found
1043 if (!fontmap || (ch <= 0xFF && fontmap->glyphs[ch].image) || (ch >= 0xE000 && ch <= 0xE0FF))
1050 map = ft2_oldstyle_map;
1052 if(!least_one || i0) // never skip the first character
1053 if(x + width_of[ch] * dw > maxwidth)
1056 break; // oops, can't draw this
1058 x += width_of[ch] * dw;
1060 if (!map || map == ft2_oldstyle_map || ch < map->start || ch >= map->start + FONT_CHARS_PER_MAP)
1062 map = FontMap_FindForChar(fontmap, ch);
1065 if (!Font_LoadMapForIndex(ft2, map_index, ch, &map))
1071 mapch = ch - map->start;
1072 if (prevch && Font_GetKerningForMap(ft2, map_index, w, h, prevch, ch, &kx, NULL))
1074 x += map->glyphs[mapch].advance_x * dw;
1083 *outcolor = colorindex;
1088 float DrawQ_Color[4];
1089 float DrawQ_String_Scale(float startx, float starty, const char *text, size_t maxlen, float w, float h, float sw, float sh, float basered, float basegreen, float baseblue, float basealpha, int flags, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt)
1091 int shadow, colorindex = STRING_COLOR_DEFAULT;
1093 float x = startx, y, s, t, u, v, thisw;
1094 Uchar ch, mapch, nextch;
1095 Uchar prevch = 0; // used for kerning
1097 //ft2_font_map_t *prevmap = NULL; // the previous map
1098 ft2_font_map_t *map = NULL; // the currently used map
1099 ft2_font_map_t *fontmap = NULL; // the font map for the size
1101 const char *text_start = text;
1103 ft2_font_t *ft2 = fnt->ft2;
1104 qboolean snap = true;
1108 const float *width_of;
1109 dp_model_t *mod = CL_Mesh_UI();
1110 msurface_t *surf = NULL;
1113 tw = Draw_GetPicWidth(fnt->pic);
1114 th = Draw_GetPicHeight(fnt->pic);
1122 starty -= (fnt->settings.scale - 1) * h * 0.5 - fnt->settings.voffset*h; // center & offset
1123 w *= fnt->settings.scale;
1124 h *= fnt->settings.scale;
1129 map_index = Font_IndexForSize(ft2, h, &w, &h);
1131 map_index = Font_IndexForSize(ft2, h, NULL, NULL);
1132 fontmap = Font_MapForIndex(ft2, map_index);
1138 // draw the font at its baseline when using freetype
1140 ftbase_y = dh * (4.5/6.0);
1145 if(!r_draw2d.integer && !r_draw2d_force)
1146 return startx + DrawQ_TextWidth_UntilWidth_TrackColors_Scale(text, &maxlen, w, h, sw, sh, NULL, ignorecolorcodes, fnt, 1000000000);
1148 //ftbase_x = snap_to_pixel_x(ftbase_x);
1151 startx = snap_to_pixel_x(startx, 0.4);
1152 starty = snap_to_pixel_y(starty, 0.4);
1153 ftbase_y = snap_to_pixel_y(ftbase_y, 0.3);
1156 pix_x = vid.width / vid_conwidth.value;
1157 pix_y = vid.height / vid_conheight.value;
1160 width_of = fontmap->width_of;
1162 width_of = fnt->width_of;
1164 for (shadow = r_textshadow.value != 0 && basealpha > 0;shadow >= 0;shadow--)
1169 if (!outcolor || *outcolor == -1)
1170 colorindex = STRING_COLOR_DEFAULT;
1172 colorindex = *outcolor;
1174 DrawQ_GetTextColor(DrawQ_Color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0);
1181 x += r_textshadow.value * vid.width / vid_conwidth.value;
1182 y += r_textshadow.value * vid.height / vid_conheight.value;
1185 while (((bytes_left = maxlen - (text - text_start)) > 0) && *text)
1187 nextch = ch = u8_getnchar(text, &text, bytes_left);
1188 i = text - text_start;
1191 if (ch == ' ' && !fontmap)
1193 x += width_of[(int) ' '] * dw;
1196 if (ch == STRING_COLOR_TAG && !ignorecolorcodes && i < maxlen)
1198 ch = *text; // colors are ascii, so no u8_ needed
1199 if (ch <= '9' && ch >= '0') // ^[0-9] found
1201 colorindex = ch - '0';
1202 DrawQ_GetTextColor(DrawQ_Color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0);
1207 else if (ch == STRING_COLOR_RGB_TAG_CHAR && i+3 < maxlen ) // ^x found
1209 const char *text_p = &text[1];
1210 int tempcolorindex = RGBstring_to_colorindex(text_p);
1213 colorindex = tempcolorindex;
1214 DrawQ_GetTextColor(DrawQ_Color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0);
1220 else if (ch == STRING_COLOR_TAG)
1229 // using a value of -1 for the oldstyle map because NULL means uninitialized...
1230 // this way we don't need to rebind fnt->tex for every old-style character
1231 // E000..E0FF: emulate old-font characters (to still have smileys and such available)
1234 x += 1.0/pix_x * r_textshadow.value;
1235 y += 1.0/pix_y * r_textshadow.value;
1237 if (!fontmap || (ch <= 0xFF && fontmap->glyphs[ch].image) || (ch >= 0xE000 && ch <= 0xE0FF))
1244 map = ft2_oldstyle_map;
1246 //num = (unsigned char) text[i];
1247 //thisw = fnt->width_of[num];
1248 thisw = fnt->width_of[ch];
1249 // FIXME make these smaller to just include the occupied part of the character for slightly faster rendering
1250 if (r_nearest_conchars.integer)
1252 s = (ch & 15)*0.0625f;
1253 t = (ch >> 4)*0.0625f;
1254 u = 0.0625f * thisw;
1259 s = (ch & 15)*0.0625f + (0.5f / tw);
1260 t = (ch >> 4)*0.0625f + (0.5f / th);
1261 u = 0.0625f * thisw - (1.0f / tw);
1262 v = 0.0625f - (1.0f / th);
1264 surf = Mod_Mesh_AddSurface(mod, Mod_Mesh_GetTexture(mod, fnt->pic->name, flags, TEXF_ALPHA | TEXF_CLAMP, MATERIALFLAG_WALL | MATERIALFLAG_VERTEXCOLOR | MATERIALFLAG_ALPHAGEN_VERTEX | MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW), true);
1265 e0 = Mod_Mesh_IndexForVertex(mod, surf, x , y , 10, 0, 0, -1, s , t , 0, 0, DrawQ_Color[0], DrawQ_Color[1], DrawQ_Color[2], DrawQ_Color[3]);
1266 e1 = Mod_Mesh_IndexForVertex(mod, surf, x+dw*thisw, y , 10, 0, 0, -1, s+u, t , 0, 0, DrawQ_Color[0], DrawQ_Color[1], DrawQ_Color[2], DrawQ_Color[3]);
1267 e2 = Mod_Mesh_IndexForVertex(mod, surf, x+dw*thisw, y+dh, 10, 0, 0, -1, s+u, t+v, 0, 0, DrawQ_Color[0], DrawQ_Color[1], DrawQ_Color[2], DrawQ_Color[3]);
1268 e3 = Mod_Mesh_IndexForVertex(mod, surf, x , y+dh, 10, 0, 0, -1, s , t+v, 0, 0, DrawQ_Color[0], DrawQ_Color[1], DrawQ_Color[2], DrawQ_Color[3]);
1269 Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
1270 Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
1271 x += width_of[ch] * dw;
1273 if (!map || map == ft2_oldstyle_map || ch < map->start || ch >= map->start + FONT_CHARS_PER_MAP)
1276 map = FontMap_FindForChar(fontmap, ch);
1279 if (!Font_LoadMapForIndex(ft2, map_index, ch, &map))
1286 // this shouldn't happen
1293 mapch = ch - map->start;
1294 thisw = map->glyphs[mapch].advance_x;
1298 if (prevch && Font_GetKerningForMap(ft2, map_index, w, h, prevch, ch, &kx, &ky))
1305 surf = Mod_Mesh_AddSurface(mod, Mod_Mesh_GetTexture(mod, map->pic->name, flags, TEXF_ALPHA | TEXF_CLAMP, MATERIALFLAG_WALL | MATERIALFLAG_VERTEXCOLOR | MATERIALFLAG_ALPHAGEN_VERTEX | MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW), true);
1306 e0 = Mod_Mesh_IndexForVertex(mod, surf, x + dw * map->glyphs[mapch].vxmin, y + dh * map->glyphs[mapch].vymin, 10, 0, 0, -1, map->glyphs[mapch].txmin, map->glyphs[mapch].tymin, 0, 0, DrawQ_Color[0], DrawQ_Color[1], DrawQ_Color[2], DrawQ_Color[3]);
1307 e1 = Mod_Mesh_IndexForVertex(mod, surf, x + dw * map->glyphs[mapch].vxmax, y + dh * map->glyphs[mapch].vymin, 10, 0, 0, -1, map->glyphs[mapch].txmax, map->glyphs[mapch].tymin, 0, 0, DrawQ_Color[0], DrawQ_Color[1], DrawQ_Color[2], DrawQ_Color[3]);
1308 e2 = Mod_Mesh_IndexForVertex(mod, surf, x + dw * map->glyphs[mapch].vxmax, y + dh * map->glyphs[mapch].vymax, 10, 0, 0, -1, map->glyphs[mapch].txmax, map->glyphs[mapch].tymax, 0, 0, DrawQ_Color[0], DrawQ_Color[1], DrawQ_Color[2], DrawQ_Color[3]);
1309 e3 = Mod_Mesh_IndexForVertex(mod, surf, x + dw * map->glyphs[mapch].vxmin, y + dh * map->glyphs[mapch].vymax, 10, 0, 0, -1, map->glyphs[mapch].txmin, map->glyphs[mapch].tymax, 0, 0, DrawQ_Color[0], DrawQ_Color[1], DrawQ_Color[2], DrawQ_Color[3]);
1310 Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
1311 Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
1323 x -= 1.0/pix_x * r_textshadow.value;
1324 y -= 1.0/pix_y * r_textshadow.value;
1330 *outcolor = colorindex;
1332 // note: this relies on the proper text (not shadow) being drawn last
1336 float DrawQ_String(float startx, float starty, const char *text, size_t maxlen, float w, float h, float basered, float basegreen, float baseblue, float basealpha, int flags, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt)
1338 return DrawQ_String_Scale(startx, starty, text, maxlen, w, h, 1, 1, basered, basegreen, baseblue, basealpha, flags, outcolor, ignorecolorcodes, fnt);
1341 float DrawQ_TextWidth_UntilWidth_TrackColors(const char *text, size_t *maxlen, float w, float h, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxwidth)
1343 return DrawQ_TextWidth_UntilWidth_TrackColors_Scale(text, maxlen, w, h, 1, 1, outcolor, ignorecolorcodes, fnt, maxwidth);
1346 float DrawQ_TextWidth(const char *text, size_t maxlen, float w, float h, qboolean ignorecolorcodes, const dp_font_t *fnt)
1348 return DrawQ_TextWidth_UntilWidth(text, &maxlen, w, h, ignorecolorcodes, fnt, 1000000000);
1351 float DrawQ_TextWidth_UntilWidth(const char *text, size_t *maxlen, float w, float h, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxWidth)
1353 return DrawQ_TextWidth_UntilWidth_TrackColors(text, maxlen, w, h, NULL, ignorecolorcodes, fnt, maxWidth);
1358 // no ^xrgb management
1359 static int DrawQ_BuildColoredText(char *output2c, size_t maxoutchars, const char *text, int maxreadchars, qboolean ignorecolorcodes, int *outcolor)
1361 int color, numchars = 0;
1362 char *outputend2c = output2c + maxoutchars - 2;
1363 if (!outcolor || *outcolor == -1)
1364 color = STRING_COLOR_DEFAULT;
1368 maxreadchars = 1<<30;
1369 textend = text + maxreadchars;
1370 while (text != textend && *text)
1372 if (*text == STRING_COLOR_TAG && !ignorecolorcodes && text + 1 != textend)
1374 if (text[1] == STRING_COLOR_TAG)
1376 else if (text[1] >= '0' && text[1] <= '9')
1378 color = text[1] - '0';
1383 if (output2c >= outputend2c)
1385 *output2c++ = *text++;
1386 *output2c++ = color;
1389 output2c[0] = output2c[1] = 0;
1396 void DrawQ_SuperPic(float x, float y, cachepic_t *pic, float width, float height, float s1, float t1, float r1, float g1, float b1, float a1, float s2, float t2, float r2, float g2, float b2, float a2, float s3, float t3, float r3, float g3, float b3, float a3, float s4, float t4, float r4, float g4, float b4, float a4, int flags)
1398 dp_model_t *mod = CL_Mesh_UI();
1402 pic = Draw_CachePic("white");
1403 // make sure pic is loaded - we don't use the texture here, Mod_Mesh_GetTexture looks up the skinframe by name
1404 Draw_GetPicTexture(pic);
1408 height = pic->height;
1409 surf = Mod_Mesh_AddSurface(mod, Mod_Mesh_GetTexture(mod, pic->name, flags, pic->texflags, MATERIALFLAG_WALL | MATERIALFLAG_VERTEXCOLOR | MATERIALFLAG_ALPHAGEN_VERTEX | MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW), true);
1410 e0 = Mod_Mesh_IndexForVertex(mod, surf, x , y , 0, 0, 0, -1, s1, t1, 0, 0, r1, g1, b1, a1);
1411 e1 = Mod_Mesh_IndexForVertex(mod, surf, x + width, y , 0, 0, 0, -1, s2, t2, 0, 0, r2, g2, b2, a2);
1412 e2 = Mod_Mesh_IndexForVertex(mod, surf, x + width, y + height, 0, 0, 0, -1, s4, t4, 0, 0, r4, g4, b4, a4);
1413 e3 = Mod_Mesh_IndexForVertex(mod, surf, x , y + height, 0, 0, 0, -1, s3, t3, 0, 0, r3, g3, b3, a3);
1414 Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
1415 Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
1418 void DrawQ_Line (float width, float x1, float y1, float x2, float y2, float r, float g, float b, float alpha, int flags)
1420 dp_model_t *mod = CL_Mesh_UI();
1423 float offsetx, offsety;
1424 // width is measured in real pixels
1425 if (fabs(x2 - x1) > fabs(y2 - y1))
1428 offsety = 0.5f * width * vid_conheight.value / vid.height;
1432 offsetx = 0.5f * width * vid_conwidth.value / vid.width;
1435 surf = Mod_Mesh_AddSurface(mod, Mod_Mesh_GetTexture(mod, "white", 0, 0, MATERIALFLAG_WALL | MATERIALFLAG_VERTEXCOLOR | MATERIALFLAG_ALPHAGEN_VERTEX | MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW), true);
1436 e0 = Mod_Mesh_IndexForVertex(mod, surf, x1 - offsetx, y1 - offsety, 10, 0, 0, -1, 0, 0, 0, 0, r, g, b, alpha);
1437 e1 = Mod_Mesh_IndexForVertex(mod, surf, x2 - offsetx, y2 - offsety, 10, 0, 0, -1, 0, 0, 0, 0, r, g, b, alpha);
1438 e2 = Mod_Mesh_IndexForVertex(mod, surf, x2 + offsetx, y2 + offsety, 10, 0, 0, -1, 0, 0, 0, 0, r, g, b, alpha);
1439 e3 = Mod_Mesh_IndexForVertex(mod, surf, x1 + offsetx, y1 + offsety, 10, 0, 0, -1, 0, 0, 0, 0, r, g, b, alpha);
1440 Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
1441 Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
1444 void DrawQ_SetClipArea(float x, float y, float width, float height)
1449 // We have to convert the con coords into real coords
1450 // OGL uses bottom to top (origin is in bottom left)
1451 ix = (int)(0.5 + x * ((float)r_refdef.view.width / vid_conwidth.integer)) + r_refdef.view.x;
1452 iy = (int)(0.5 + y * ((float)r_refdef.view.height / vid_conheight.integer)) + r_refdef.view.y;
1453 iw = (int)(0.5 + width * ((float)r_refdef.view.width / vid_conwidth.integer));
1454 ih = (int)(0.5 + height * ((float)r_refdef.view.height / vid_conheight.integer));
1455 switch(vid.renderpath)
1457 case RENDERPATH_GL32:
1458 case RENDERPATH_GLES2:
1459 GL_Scissor(ix, vid.height - iy - ih, iw, ih);
1463 GL_ScissorTest(true);
1466 void DrawQ_ResetClipArea(void)
1469 GL_ScissorTest(false);
1472 void DrawQ_Finish(void)
1475 r_refdef.draw2dstage = 0;
1478 void DrawQ_RecalcView(void)
1481 if(r_refdef.draw2dstage)
1482 r_refdef.draw2dstage = -1; // next draw call will set viewport etc. again
1485 void DrawQ_FlushUI(void)
1487 dp_model_t *mod = CL_Mesh_UI();
1488 if (mod->num_surfaces == 0)
1491 if (!r_draw2d.integer && !r_draw2d_force)
1493 Mod_Mesh_Reset(mod);
1497 // this is roughly equivalent to R_Mod_Draw, so the UI can use full material feature set
1498 r_refdef.view.colorscale = 1;
1499 r_textureframe++; // used only by R_GetCurrentTexture
1500 GL_DepthMask(false);
1502 Mod_Mesh_Finalize(mod);
1503 R_DrawModelSurfaces(&cl_meshentities[MESH_UI].render, false, false, false, false, false, true);
1505 Mod_Mesh_Reset(mod);