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_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_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_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_SAVE, "r_font_postprocess_blur", "0", "font blur amount"};
58 cvar_t r_font_postprocess_outline = {CVAR_SAVE, "r_font_postprocess_outline", "0", "font outline amount"};
59 cvar_t r_font_postprocess_shadow_x = {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_SAVE, "r_font_postprocess_shadow_y", "0", "font shadow Y shift amount, applied during outlining"};
61 cvar_t r_font_postprocess_shadow_z = {CVAR_SAVE, "r_font_postprocess_shadow_z", "0", "font shadow Z shift amount, applied during blurring"};
62 cvar_t r_font_hinting = {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_SAVE, "r_font_antialias", "1", "0 = monochrome, 1 = grey" /* , 2 = rgb, 3 = bgr" */};
64 cvar_t r_nearest_2d = {CVAR_SAVE, "r_nearest_2d", "0", "use nearest filtering on all 2d textures (including conchars)"};
65 cvar_t r_nearest_conchars = {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_NEAREST) || r_nearest_2d.integer)
98 texflags |= TEXF_FORCENEAREST;
100 // check whether the picture has already been cached
101 crc = CRC_Block((unsigned char *)path, strlen(path));
102 hashkey = ((crc >> 8) ^ crc) % CACHEPICHASHSIZE;
103 for (pic = cachepichash[hashkey];pic;pic = pic->chain)
105 if (!strcmp(path, pic->name))
107 // if it was created (or replaced) by Draw_NewPic, just return it
108 if (!(pic->flags & CACHEPICFLAG_NEWPIC))
110 // reload the pic if texflags changed in important ways
111 // ignore TEXF_COMPRESS when comparing, because fallback pics remove the flag, and ignore TEXF_MIPMAP because QC specifies that
112 if (!pic->skinframe || !pic->skinframe->base || ((pic->texflags ^ texflags) & ~(TEXF_COMPRESS | TEXF_MIPMAP)))
114 if (!(cachepicflags & CACHEPICFLAG_NOTPERSISTENT))
115 pic->autoload = false; // caller is making this pic persistent
118 R_SkinFrame_MarkUsed(pic->skinframe);
119 pic->lastusedframe = draw_frame;
124 if (numcachepics == MAX_CACHED_PICS)
126 Con_Printf ("Draw_CachePic: numcachepics == MAX_CACHED_PICS\n");
127 // FIXME: support NULL in callers?
128 return cachepics; // return the first one
130 pic = cachepics + (numcachepics++);
131 memset(pic, 0, sizeof(*pic));
132 strlcpy (pic->name, path, sizeof(pic->name));
134 pic->chain = cachepichash[hashkey];
135 cachepichash[hashkey] = pic;
139 R_SkinFrame_PurgeSkinFrame(pic->skinframe);
141 pic->flags = cachepicflags;
142 pic->texflags = texflags;
143 pic->autoload = (cachepicflags & CACHEPICFLAG_NOTPERSISTENT) != 0;
144 pic->lastusedframe = draw_frame;
148 // reload image after it was unloaded or texflags changed significantly
149 R_SkinFrame_LoadExternal_SkinFrame(pic->skinframe, pic->name, texflags | TEXF_FORCE_RELOAD, (cachepicflags & CACHEPICFLAG_QUIET) == 0, (cachepicflags & CACHEPICFLAG_FAILONMISSING) == 0);
153 // load high quality image (this falls back to low quality too)
154 pic->skinframe = R_SkinFrame_LoadExternal(pic->name, texflags | TEXF_FORCE_RELOAD, (cachepicflags & CACHEPICFLAG_QUIET) == 0, (cachepicflags & CACHEPICFLAG_FAILONMISSING) == 0);
157 // get the dimensions of the image we loaded (if it was successful)
158 if (pic->skinframe && pic->skinframe->base)
160 pic->width = R_TextureWidth(pic->skinframe->base);
161 pic->height = R_TextureHeight(pic->skinframe->base);
164 // check for a low quality version of the pic and use its size if possible, to match the stock hud
165 Image_GetStockPicSize(pic->name, &pic->width, &pic->height);
170 cachepic_t *Draw_CachePic (const char *path)
172 return Draw_CachePic_Flags (path, 0); // default to persistent!
175 const char *Draw_GetPicName(cachepic_t *pic)
182 int Draw_GetPicWidth(cachepic_t *pic)
189 int Draw_GetPicHeight(cachepic_t *pic)
196 qboolean Draw_IsPicLoaded(cachepic_t *pic)
200 if (pic->autoload && (!pic->skinframe || !pic->skinframe->base))
201 pic->skinframe = R_SkinFrame_LoadExternal(pic->name, pic->texflags | TEXF_FORCE_RELOAD, false, true);
202 // skinframe will only be NULL if the pic was created with CACHEPICFLAG_FAILONMISSING and not found
203 return pic->skinframe != NULL && pic->skinframe->base != NULL;
206 rtexture_t *Draw_GetPicTexture(cachepic_t *pic)
210 if (pic->autoload && (!pic->skinframe || !pic->skinframe->base))
211 pic->skinframe = R_SkinFrame_LoadExternal(pic->name, pic->texflags | TEXF_FORCE_RELOAD, false, true);
212 pic->lastusedframe = draw_frame;
213 return pic->skinframe ? pic->skinframe->base : NULL;
216 void Draw_Frame(void)
220 static double nextpurgetime;
221 if (nextpurgetime > realtime)
223 nextpurgetime = realtime + 0.05;
224 for (i = 0, pic = cachepics;i < numcachepics;i++, pic++)
225 if (pic->autoload && pic->skinframe && pic->skinframe->base && pic->lastusedframe < draw_frame)
226 R_SkinFrame_PurgeSkinFrame(pic->skinframe);
230 cachepic_t *Draw_NewPic(const char *picname, int width, int height, unsigned char *pixels_bgra, textype_t textype, int texflags)
235 crc = CRC_Block((unsigned char *)picname, strlen(picname));
236 hashkey = ((crc >> 8) ^ crc) % CACHEPICHASHSIZE;
237 for (pic = cachepichash[hashkey];pic;pic = pic->chain)
238 if (!strcmp (picname, pic->name))
243 if (pic->flags & CACHEPICFLAG_NEWPIC && pic->skinframe && pic->skinframe->base && pic->width == width && pic->height == height)
245 R_UpdateTexture(pic->skinframe->base, pixels_bgra, 0, 0, 0, width, height, 1);
246 R_SkinFrame_MarkUsed(pic->skinframe);
247 pic->lastusedframe = draw_frame;
253 if (numcachepics == MAX_CACHED_PICS)
255 Con_Printf ("Draw_NewPic: numcachepics == MAX_CACHED_PICS\n");
256 // FIXME: support NULL in callers?
257 return cachepics; // return the first one
259 pic = cachepics + (numcachepics++);
260 memset(pic, 0, sizeof(*pic));
261 strlcpy (pic->name, picname, sizeof(pic->name));
263 pic->chain = cachepichash[hashkey];
264 cachepichash[hashkey] = pic;
267 R_SkinFrame_PurgeSkinFrame(pic->skinframe);
269 pic->autoload = false;
270 pic->flags = CACHEPICFLAG_NEWPIC; // disable texflags checks in Draw_CachePic
271 pic->flags |= (texflags & TEXF_CLAMP) ? 0 : CACHEPICFLAG_NOCLAMP;
272 pic->flags |= (texflags & TEXF_FORCENEAREST) ? CACHEPICFLAG_NEAREST : 0;
274 pic->height = height;
275 pic->skinframe = R_SkinFrame_LoadInternalBGRA(picname, texflags | TEXF_FORCE_RELOAD, pixels_bgra, width, height, vid.sRGB2D);
276 pic->lastusedframe = draw_frame;
280 void Draw_FreePic(const char *picname)
285 // this doesn't really free the pic, but does free its texture
286 crc = CRC_Block((unsigned char *)picname, strlen(picname));
287 hashkey = ((crc >> 8) ^ crc) % CACHEPICHASHSIZE;
288 for (pic = cachepichash[hashkey];pic;pic = pic->chain)
290 if (!strcmp (picname, pic->name) && pic->skinframe)
292 R_SkinFrame_PurgeSkinFrame(pic->skinframe);
300 static float snap_to_pixel_x(float x, float roundUpAt);
301 extern int con_linewidth; // to force rewrapping
302 void LoadFont(qboolean override, const char *name, dp_font_t *fnt, float scale, float voffset)
306 char widthfile[MAX_QPATH];
308 fs_offset_t widthbufsize;
310 if(override || !fnt->texpath[0])
312 strlcpy(fnt->texpath, name, sizeof(fnt->texpath));
313 // load the cvars when the font is FIRST loader
314 fnt->settings.scale = scale;
315 fnt->settings.voffset = voffset;
316 fnt->settings.antialias = r_font_antialias.integer;
317 fnt->settings.hinting = r_font_hinting.integer;
318 fnt->settings.outline = r_font_postprocess_outline.value;
319 fnt->settings.blur = r_font_postprocess_blur.value;
320 fnt->settings.shadowx = r_font_postprocess_shadow_x.value;
321 fnt->settings.shadowy = r_font_postprocess_shadow_y.value;
322 fnt->settings.shadowz = r_font_postprocess_shadow_z.value;
325 if (fnt->settings.scale <= 0)
326 fnt->settings.scale = 1;
328 if(drawtexturepool == NULL)
329 return; // before gl_draw_start, so will be loaded later
333 // clear freetype font
334 Font_UnloadFont(fnt->ft2);
339 if(fnt->req_face != -1)
341 if(!Font_LoadFont(fnt->texpath, fnt))
342 Con_DPrintf("Failed to load font-file for '%s', it will not support as many characters.\n", fnt->texpath);
345 fnt->pic = Draw_CachePic_Flags(fnt->texpath, CACHEPICFLAG_QUIET | CACHEPICFLAG_NOCOMPRESSION | (r_nearest_conchars.integer ? CACHEPICFLAG_NEAREST : 0) | CACHEPICFLAG_FAILONMISSING);
346 if(!Draw_IsPicLoaded(fnt->pic))
348 for (i = 0; i < MAX_FONT_FALLBACKS; ++i)
350 if (!fnt->fallbacks[i][0])
352 fnt->pic = Draw_CachePic_Flags(fnt->fallbacks[i], CACHEPICFLAG_QUIET | CACHEPICFLAG_NOCOMPRESSION | (r_nearest_conchars.integer ? CACHEPICFLAG_NEAREST : 0) | CACHEPICFLAG_FAILONMISSING);
353 if(Draw_IsPicLoaded(fnt->pic))
356 if(!Draw_IsPicLoaded(fnt->pic))
358 fnt->pic = Draw_CachePic_Flags("gfx/conchars", CACHEPICFLAG_NOCOMPRESSION | (r_nearest_conchars.integer ? CACHEPICFLAG_NEAREST : 0));
359 strlcpy(widthfile, "gfx/conchars.width", sizeof(widthfile));
362 dpsnprintf(widthfile, sizeof(widthfile), "%s.width", fnt->fallbacks[i]);
365 dpsnprintf(widthfile, sizeof(widthfile), "%s.width", fnt->texpath);
367 // unspecified width == 1 (base width)
368 for(ch = 0; ch < 256; ++ch)
369 fnt->width_of[ch] = 1;
371 // FIXME load "name.width", if it fails, fill all with 1
372 if((widthbuf = (char *) FS_LoadFile(widthfile, tempmempool, true, &widthbufsize)))
374 float extraspacing = 0;
375 const char *p = widthbuf;
380 if(!COM_ParseToken_Simple(&p, false, false, true))
398 fnt->width_of[ch] = atof(com_token) + extraspacing;
402 if(!strcmp(com_token, "extraspacing"))
404 if(!COM_ParseToken_Simple(&p, false, false, true))
406 extraspacing = atof(com_token);
408 else if(!strcmp(com_token, "scale"))
410 if(!COM_ParseToken_Simple(&p, false, false, true))
412 fnt->settings.scale = atof(com_token);
416 Con_Printf("Warning: skipped unknown font property %s\n", com_token);
417 if(!COM_ParseToken_Simple(&p, false, false, true))
429 for (i = 0; i < MAX_FONT_SIZES; ++i)
431 ft2_font_map_t *map = Font_MapForIndex(fnt->ft2, i);
434 for(ch = 0; ch < 256; ++ch)
435 map->width_of[ch] = Font_SnapTo(fnt->width_of[ch], 1/map->size);
439 maxwidth = fnt->width_of[0];
440 for(i = 1; i < 256; ++i)
441 maxwidth = max(maxwidth, fnt->width_of[i]);
442 fnt->maxwidth = maxwidth;
444 // fix up maxwidth for overlap
445 fnt->maxwidth *= fnt->settings.scale;
447 if(fnt == FONT_CONSOLE)
448 con_linewidth = -1; // rewrap console in next frame
451 extern cvar_t developer_font;
452 dp_font_t *FindFont(const char *title, qboolean allocate_new)
457 for(i = 0; i < dp_fonts.maxsize; ++i)
458 if(!strcmp(dp_fonts.f[i].title, title))
459 return &dp_fonts.f[i];
460 // if not found - try allocate
463 // find any font with empty title
464 for(i = 0; i < dp_fonts.maxsize; ++i)
466 if(!strcmp(dp_fonts.f[i].title, ""))
468 strlcpy(dp_fonts.f[i].title, title, sizeof(dp_fonts.f[i].title));
469 return &dp_fonts.f[i];
472 // if no any 'free' fonts - expand buffer
473 oldsize = dp_fonts.maxsize;
474 dp_fonts.maxsize = dp_fonts.maxsize + FONTS_EXPAND;
475 if (developer_font.integer)
476 Con_Printf("FindFont: enlarging fonts buffer (%i -> %i)\n", oldsize, dp_fonts.maxsize);
477 dp_fonts.f = (dp_font_t *)Mem_Realloc(fonts_mempool, dp_fonts.f, sizeof(dp_font_t) * dp_fonts.maxsize);
478 // relink ft2 structures
479 for(i = 0; i < oldsize; ++i)
480 if (dp_fonts.f[i].ft2)
481 dp_fonts.f[i].ft2->settings = &dp_fonts.f[i].settings;
482 // register a font in first expanded slot
483 strlcpy(dp_fonts.f[oldsize].title, title, sizeof(dp_fonts.f[oldsize].title));
484 return &dp_fonts.f[oldsize];
489 static float snap_to_pixel_x(float x, float roundUpAt)
491 float pixelpos = x * vid.width / vid_conwidth.value;
492 int snap = (int) pixelpos;
493 if (pixelpos - snap >= roundUpAt) ++snap;
494 return ((float)snap * vid_conwidth.value / vid.width);
496 x = (int)(x * vid.width / vid_conwidth.value);
497 x = (x * vid_conwidth.value / vid.width);
502 static float snap_to_pixel_y(float y, float roundUpAt)
504 float pixelpos = y * vid.height / vid_conheight.value;
505 int snap = (int) pixelpos;
506 if (pixelpos - snap > roundUpAt) ++snap;
507 return ((float)snap * vid_conheight.value / vid.height);
509 y = (int)(y * vid.height / vid_conheight.value);
510 y = (y * vid_conheight.value / vid.height);
515 static void LoadFont_f(void)
519 const char *filelist, *c, *cm;
520 float sz, scale, voffset;
521 char mainfont[MAX_QPATH];
525 Con_Printf("Available font commands:\n");
526 for(i = 0; i < dp_fonts.maxsize; ++i)
527 if (dp_fonts.f[i].title[0])
528 Con_Printf(" loadfont %s gfx/tgafile[...] [custom switches] [sizes...]\n", dp_fonts.f[i].title);
529 Con_Printf("A font can simply be gfx/tgafile, or alternatively you\n"
530 "can specify multiple fonts and faces\n"
531 "Like this: gfx/vera-sans:2,gfx/fallback:1\n"
532 "to load face 2 of the font gfx/vera-sans and use face 1\n"
533 "of gfx/fallback as fallback font.\n"
534 "You can also specify a list of font sizes to load, like this:\n"
535 "loadfont console gfx/conchars,gfx/fallback 8 12 16 24 32\n"
536 "In many cases, 8 12 16 24 32 should be a good choice.\n"
538 " scale x : scale all characters by this amount when rendering (doesnt change line height)\n"
539 " voffset x : offset all chars vertical when rendering, this is multiplied to character height\n"
543 f = FindFont(Cmd_Argv(1), true);
546 Con_Printf("font function not found\n");
551 filelist = "gfx/conchars";
553 filelist = Cmd_Argv(2);
555 memset(f->fallbacks, 0, sizeof(f->fallbacks));
556 memset(f->fallback_faces, 0, sizeof(f->fallback_faces));
558 // first font is handled "normally"
559 c = strchr(filelist, ':');
560 cm = strchr(filelist, ',');
561 if(c && (!cm || c < cm))
562 f->req_face = atoi(c+1);
569 if(!c || (c - filelist) > MAX_QPATH)
570 strlcpy(mainfont, filelist, sizeof(mainfont));
573 memcpy(mainfont, filelist, c - filelist);
574 mainfont[c - filelist] = 0;
577 for(i = 0; i < MAX_FONT_FALLBACKS; ++i)
579 c = strchr(filelist, ',');
585 c = strchr(filelist, ':');
586 cm = strchr(filelist, ',');
587 if(c && (!cm || c < cm))
588 f->fallback_faces[i] = atoi(c+1);
591 f->fallback_faces[i] = 0; // f->req_face; could make it stick to the default-font's face index
594 if(!c || (c-filelist) > MAX_QPATH)
596 strlcpy(f->fallbacks[i], filelist, sizeof(mainfont));
600 memcpy(f->fallbacks[i], filelist, c - filelist);
601 f->fallbacks[i][c - filelist] = 0;
605 // for now: by default load only one size: the default size
607 for(i = 1; i < MAX_FONT_SIZES; ++i)
608 f->req_sizes[i] = -1;
614 for(sizes = 0, i = 3; i < Cmd_Argc(); ++i)
617 if (!strcmp(Cmd_Argv(i), "scale"))
621 scale = atof(Cmd_Argv(i));
624 if (!strcmp(Cmd_Argv(i), "voffset"))
628 voffset = atof(Cmd_Argv(i));
633 continue; // no slot for other sizes
635 // parse one of sizes
636 sz = atof(Cmd_Argv(i));
637 if (sz > 0.001f && sz < 1000.0f) // do not use crap sizes
639 // search for duplicated sizes
641 for (j=0; j<sizes; j++)
642 if (f->req_sizes[j] == sz)
645 continue; // sz already in req_sizes, don't add it again
647 if (sizes == MAX_FONT_SIZES)
649 Con_Printf("Warning: specified more than %i different font sizes, exceding ones are ignored\n", MAX_FONT_SIZES);
653 f->req_sizes[sizes] = sz;
659 LoadFont(true, mainfont, f, scale, voffset);
667 static void gl_draw_start(void)
671 drawtexturepool = R_AllocTexturePool();
674 memset(cachepichash, 0, sizeof(cachepichash));
678 // load default font textures
679 for(i = 0; i < dp_fonts.maxsize; ++i)
680 if (dp_fonts.f[i].title[0])
681 LoadFont(false, va(vabuf, sizeof(vabuf), "gfx/font_%s", dp_fonts.f[i].title), &dp_fonts.f[i], 1, 0);
683 // draw the loading screen so people have something to see in the newly opened window
684 SCR_UpdateLoadingScreen(true, true);
687 static void gl_draw_shutdown(void)
691 R_FreeTexturePool(&drawtexturepool);
694 memset(cachepichash, 0, sizeof(cachepichash));
697 static void gl_draw_newmap(void)
702 // mark all of the persistent pics so they are not purged...
703 for (i = 0; i < numcachepics; i++)
705 cachepic_t *pic = cachepics + i;
706 if (!pic->autoload && pic->skinframe)
707 R_SkinFrame_MarkUsed(pic->skinframe);
711 void GL_Draw_Init (void)
715 Cvar_RegisterVariable(&r_font_postprocess_blur);
716 Cvar_RegisterVariable(&r_font_postprocess_outline);
717 Cvar_RegisterVariable(&r_font_postprocess_shadow_x);
718 Cvar_RegisterVariable(&r_font_postprocess_shadow_y);
719 Cvar_RegisterVariable(&r_font_postprocess_shadow_z);
720 Cvar_RegisterVariable(&r_font_hinting);
721 Cvar_RegisterVariable(&r_font_antialias);
722 Cvar_RegisterVariable(&r_textshadow);
723 Cvar_RegisterVariable(&r_textbrightness);
724 Cvar_RegisterVariable(&r_textcontrast);
725 Cvar_RegisterVariable(&r_nearest_2d);
726 Cvar_RegisterVariable(&r_nearest_conchars);
728 // allocate fonts storage
729 fonts_mempool = Mem_AllocPool("FONTS", 0, NULL);
730 dp_fonts.maxsize = MAX_FONTS;
731 dp_fonts.f = (dp_font_t *)Mem_Alloc(fonts_mempool, sizeof(dp_font_t) * dp_fonts.maxsize);
732 memset(dp_fonts.f, 0, sizeof(dp_font_t) * dp_fonts.maxsize);
734 // assign starting font names
735 strlcpy(FONT_DEFAULT->title, "default", sizeof(FONT_DEFAULT->title));
736 strlcpy(FONT_DEFAULT->texpath, "gfx/conchars", sizeof(FONT_DEFAULT->texpath));
737 strlcpy(FONT_CONSOLE->title, "console", sizeof(FONT_CONSOLE->title));
738 strlcpy(FONT_SBAR->title, "sbar", sizeof(FONT_SBAR->title));
739 strlcpy(FONT_NOTIFY->title, "notify", sizeof(FONT_NOTIFY->title));
740 strlcpy(FONT_CHAT->title, "chat", sizeof(FONT_CHAT->title));
741 strlcpy(FONT_CENTERPRINT->title, "centerprint", sizeof(FONT_CENTERPRINT->title));
742 strlcpy(FONT_INFOBAR->title, "infobar", sizeof(FONT_INFOBAR->title));
743 strlcpy(FONT_MENU->title, "menu", sizeof(FONT_MENU->title));
744 for(i = 0, j = 0; i < MAX_USERFONTS; ++i)
745 if(!FONT_USER(i)->title[0])
746 dpsnprintf(FONT_USER(i)->title, sizeof(FONT_USER(i)->title), "user%d", j++);
748 Cmd_AddCommand ("loadfont",LoadFont_f, "loadfont function tganame loads a font; example: loadfont console gfx/veramono; loadfont without arguments lists the available functions");
749 R_RegisterModule("GL_Draw", gl_draw_start, gl_draw_shutdown, gl_draw_newmap, NULL, NULL);
752 void DrawQ_Start(void)
754 r_refdef.draw2dstage = 1;
755 R_ResetViewRendering2D_Common(0, NULL, NULL, 0, 0, vid.width, vid.height, vid_conwidth.integer, vid_conheight.integer);
758 qboolean r_draw2d_force = false;
760 void DrawQ_Pic(float x, float y, cachepic_t *pic, float width, float height, float red, float green, float blue, float alpha, int flags)
762 dp_model_t *mod = CL_Mesh_UI();
766 pic = Draw_CachePic("white");
767 // make sure pic is loaded - we don't use the texture here, Mod_Mesh_GetTexture looks up the skinframe by name
768 Draw_GetPicTexture(pic);
772 height = pic->height;
773 surf = Mod_Mesh_AddSurface(mod, Mod_Mesh_GetTexture(mod, pic->name, flags, pic->texflags, MATERIALFLAG_VERTEXCOLOR), true);
774 e0 = Mod_Mesh_IndexForVertex(mod, surf, x , y , 0, 0, 0, -1, 0, 0, 0, 0, red, green, blue, alpha);
775 e1 = Mod_Mesh_IndexForVertex(mod, surf, x + width, y , 0, 0, 0, -1, 1, 0, 0, 0, red, green, blue, alpha);
776 e2 = Mod_Mesh_IndexForVertex(mod, surf, x + width, y + height, 0, 0, 0, -1, 1, 1, 0, 0, red, green, blue, alpha);
777 e3 = Mod_Mesh_IndexForVertex(mod, surf, x , y + height, 0, 0, 0, -1, 0, 1, 0, 0, red, green, blue, alpha);
778 Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
779 Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
782 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)
784 float af = DEG2RAD(-angle); // forward
785 float ar = DEG2RAD(-angle + 90); // right
786 float sinaf = sin(af);
787 float cosaf = cos(af);
788 float sinar = sin(ar);
789 float cosar = cos(ar);
790 dp_model_t *mod = CL_Mesh_UI();
794 pic = Draw_CachePic("white");
795 // make sure pic is loaded - we don't use the texture here, Mod_Mesh_GetTexture looks up the skinframe by name
796 Draw_GetPicTexture(pic);
800 height = pic->height;
801 surf = Mod_Mesh_AddSurface(mod, Mod_Mesh_GetTexture(mod, pic->name, flags, pic->texflags, MATERIALFLAG_VERTEXCOLOR), true);
802 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);
803 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);
804 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);
805 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);
806 Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
807 Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
810 void DrawQ_Fill(float x, float y, float width, float height, float red, float green, float blue, float alpha, int flags)
812 DrawQ_Pic(x, y, Draw_CachePic("white"), width, height, red, green, blue, alpha, flags);
815 /// color tag printing
816 static const vec4_t string_colors[] =
819 // LordHavoc: why on earth is cyan before magenta in Quake3?
820 // LordHavoc: note: Doom3 uses white for [0] and [7]
821 {0.0, 0.0, 0.0, 1.0}, // black
822 {1.0, 0.0, 0.0, 1.0}, // red
823 {0.0, 1.0, 0.0, 1.0}, // green
824 {1.0, 1.0, 0.0, 1.0}, // yellow
825 {0.0, 0.0, 1.0, 1.0}, // blue
826 {0.0, 1.0, 1.0, 1.0}, // cyan
827 {1.0, 0.0, 1.0, 1.0}, // magenta
828 {1.0, 1.0, 1.0, 1.0}, // white
829 // [515]'s BX_COLOREDTEXT extension
830 {1.0, 1.0, 1.0, 0.5}, // half transparent
831 {0.5, 0.5, 0.5, 1.0} // half brightness
832 // Black's color table
833 //{1.0, 1.0, 1.0, 1.0},
834 //{1.0, 0.0, 0.0, 1.0},
835 //{0.0, 1.0, 0.0, 1.0},
836 //{0.0, 0.0, 1.0, 1.0},
837 //{1.0, 1.0, 0.0, 1.0},
838 //{0.0, 1.0, 1.0, 1.0},
839 //{1.0, 0.0, 1.0, 1.0},
840 //{0.1, 0.1, 0.1, 1.0}
843 #define STRING_COLORS_COUNT (sizeof(string_colors) / sizeof(vec4_t))
845 static void DrawQ_GetTextColor(float color[4], int colorindex, float r, float g, float b, float a, qboolean shadow)
847 float C = r_textcontrast.value;
848 float B = r_textbrightness.value;
849 if (colorindex & 0x10000) // that bit means RGB color
851 color[0] = ((colorindex >> 12) & 0xf) / 15.0;
852 color[1] = ((colorindex >> 8) & 0xf) / 15.0;
853 color[2] = ((colorindex >> 4) & 0xf) / 15.0;
854 color[3] = (colorindex & 0xf) / 15.0;
857 Vector4Copy(string_colors[colorindex], color);
858 Vector4Set(color, color[0] * r * C + B, color[1] * g * C + B, color[2] * b * C + B, color[3] * a);
861 float shadowalpha = (color[0]+color[1]+color[2]) * 0.8;
862 Vector4Set(color, 0, 0, 0, color[3] * bound(0, shadowalpha, 1));
866 // NOTE: this function always draws exactly one character if maxwidth <= 0
867 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)
869 const char *text_start = text;
870 int colorindex = STRING_COLOR_DEFAULT;
873 Uchar ch, mapch, nextch;
874 Uchar prevch = 0; // used for kerning
879 ft2_font_map_t *fontmap = NULL;
880 ft2_font_map_t *map = NULL;
881 //ft2_font_map_t *prevmap = NULL;
882 ft2_font_t *ft2 = fnt->ft2;
884 qboolean snap = true;
885 qboolean least_one = false;
886 float dw; // display w
887 //float dh; // display h
888 const float *width_of;
895 // do this in the end
896 w *= fnt->settings.scale;
897 h *= fnt->settings.scale;
899 // find the most fitting size:
903 map_index = Font_IndexForSize(ft2, h, &w, &h);
905 map_index = Font_IndexForSize(ft2, h, NULL, NULL);
906 fontmap = Font_MapForIndex(ft2, map_index);
915 if (!outcolor || *outcolor == -1)
916 colorindex = STRING_COLOR_DEFAULT;
918 colorindex = *outcolor;
920 // maxwidth /= fnt->scale; // w and h are multiplied by it already
921 // ftbase_x = snap_to_pixel_x(0);
926 maxwidth = -maxwidth;
930 // x = snap_to_pixel_x(x, 0.4); // haha, it's 0 anyway
933 width_of = fontmap->width_of;
935 width_of = fnt->width_of;
938 while (((bytes_left = *maxlen - (text - text_start)) > 0) && *text)
941 nextch = ch = u8_getnchar(text, &text, bytes_left);
942 i = text - text_start;
945 if (ch == ' ' && !fontmap)
947 if(!least_one || i0) // never skip the first character
948 if(x + width_of[(int) ' '] * dw > maxwidth)
951 break; // oops, can't draw this
953 x += width_of[(int) ' '] * dw;
956 // i points to the char after ^
957 if (ch == STRING_COLOR_TAG && !ignorecolorcodes && i < *maxlen)
959 ch = *text; // colors are ascii, so no u8_ needed
960 if (ch <= '9' && ch >= '0') // ^[0-9] found
962 colorindex = ch - '0';
967 // i points to the char after ^...
968 // i+3 points to 3 in ^x123
969 // i+3 == *maxlen would mean that char is missing
970 else if (ch == STRING_COLOR_RGB_TAG_CHAR && i + 3 < *maxlen ) // ^x found
972 // building colorindex...
973 ch = tolower(text[1]);
974 tempcolorindex = 0x10000; // binary: 1,0000,0000,0000,0000
975 if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 12;
976 else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 12;
977 else tempcolorindex = 0;
980 ch = tolower(text[2]);
981 if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 8;
982 else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 8;
983 else tempcolorindex = 0;
986 ch = tolower(text[3]);
987 if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 4;
988 else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 4;
989 else tempcolorindex = 0;
992 colorindex = tempcolorindex | 0xf;
993 // ...done! now colorindex has rgba codes (1,rrrr,gggg,bbbb,aaaa)
1001 else if (ch == STRING_COLOR_TAG) // ^^ found, ignore the first ^ and go to print the second
1010 if (!fontmap || (ch <= 0xFF && fontmap->glyphs[ch].image) || (ch >= 0xE000 && ch <= 0xE0FF))
1017 map = ft2_oldstyle_map;
1019 if(!least_one || i0) // never skip the first character
1020 if(x + width_of[ch] * dw > maxwidth)
1023 break; // oops, can't draw this
1025 x += width_of[ch] * dw;
1027 if (!map || map == ft2_oldstyle_map || ch < map->start || ch >= map->start + FONT_CHARS_PER_MAP)
1029 map = FontMap_FindForChar(fontmap, ch);
1032 if (!Font_LoadMapForIndex(ft2, map_index, ch, &map))
1038 mapch = ch - map->start;
1039 if (prevch && Font_GetKerningForMap(ft2, map_index, w, h, prevch, ch, &kx, NULL))
1041 x += map->glyphs[mapch].advance_x * dw;
1050 *outcolor = colorindex;
1055 float DrawQ_Color[4];
1056 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)
1058 int shadow, colorindex = STRING_COLOR_DEFAULT;
1060 float x = startx, y, s, t, u, v, thisw;
1061 Uchar ch, mapch, nextch;
1062 Uchar prevch = 0; // used for kerning
1065 //ft2_font_map_t *prevmap = NULL; // the previous map
1066 ft2_font_map_t *map = NULL; // the currently used map
1067 ft2_font_map_t *fontmap = NULL; // the font map for the size
1069 const char *text_start = text;
1071 ft2_font_t *ft2 = fnt->ft2;
1072 qboolean snap = true;
1076 const float *width_of;
1077 dp_model_t *mod = CL_Mesh_UI();
1078 msurface_t *surf = NULL;
1081 tw = Draw_GetPicWidth(fnt->pic);
1082 th = Draw_GetPicHeight(fnt->pic);
1090 starty -= (fnt->settings.scale - 1) * h * 0.5 - fnt->settings.voffset*h; // center & offset
1091 w *= fnt->settings.scale;
1092 h *= fnt->settings.scale;
1097 map_index = Font_IndexForSize(ft2, h, &w, &h);
1099 map_index = Font_IndexForSize(ft2, h, NULL, NULL);
1100 fontmap = Font_MapForIndex(ft2, map_index);
1106 // draw the font at its baseline when using freetype
1108 ftbase_y = dh * (4.5/6.0);
1113 if(!r_draw2d.integer && !r_draw2d_force)
1114 return startx + DrawQ_TextWidth_UntilWidth_TrackColors_Scale(text, &maxlen, w, h, sw, sh, NULL, ignorecolorcodes, fnt, 1000000000);
1116 //ftbase_x = snap_to_pixel_x(ftbase_x);
1119 startx = snap_to_pixel_x(startx, 0.4);
1120 starty = snap_to_pixel_y(starty, 0.4);
1121 ftbase_y = snap_to_pixel_y(ftbase_y, 0.3);
1124 pix_x = vid.width / vid_conwidth.value;
1125 pix_y = vid.height / vid_conheight.value;
1128 width_of = fontmap->width_of;
1130 width_of = fnt->width_of;
1132 for (shadow = r_textshadow.value != 0 && basealpha > 0;shadow >= 0;shadow--)
1137 if (!outcolor || *outcolor == -1)
1138 colorindex = STRING_COLOR_DEFAULT;
1140 colorindex = *outcolor;
1142 DrawQ_GetTextColor(DrawQ_Color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0);
1149 x += r_textshadow.value * vid.width / vid_conwidth.value;
1150 y += r_textshadow.value * vid.height / vid_conheight.value;
1153 while (((bytes_left = maxlen - (text - text_start)) > 0) && *text)
1155 nextch = ch = u8_getnchar(text, &text, bytes_left);
1156 i = text - text_start;
1159 if (ch == ' ' && !fontmap)
1161 x += width_of[(int) ' '] * dw;
1164 if (ch == STRING_COLOR_TAG && !ignorecolorcodes && i < maxlen)
1166 ch = *text; // colors are ascii, so no u8_ needed
1167 if (ch <= '9' && ch >= '0') // ^[0-9] found
1169 colorindex = ch - '0';
1170 DrawQ_GetTextColor(DrawQ_Color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0);
1175 else if (ch == STRING_COLOR_RGB_TAG_CHAR && i+3 < maxlen ) // ^x found
1177 // building colorindex...
1178 ch = tolower(text[1]);
1179 tempcolorindex = 0x10000; // binary: 1,0000,0000,0000,0000
1180 if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 12;
1181 else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 12;
1182 else tempcolorindex = 0;
1185 ch = tolower(text[2]);
1186 if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 8;
1187 else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 8;
1188 else tempcolorindex = 0;
1191 ch = tolower(text[3]);
1192 if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 4;
1193 else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 4;
1194 else tempcolorindex = 0;
1197 colorindex = tempcolorindex | 0xf;
1198 // ...done! now colorindex has rgba codes (1,rrrr,gggg,bbbb,aaaa)
1199 //Con_Printf("^1colorindex:^7 %x\n", colorindex);
1200 DrawQ_GetTextColor(DrawQ_Color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0);
1208 else if (ch == STRING_COLOR_TAG)
1217 // using a value of -1 for the oldstyle map because NULL means uninitialized...
1218 // this way we don't need to rebind fnt->tex for every old-style character
1219 // E000..E0FF: emulate old-font characters (to still have smileys and such available)
1222 x += 1.0/pix_x * r_textshadow.value;
1223 y += 1.0/pix_y * r_textshadow.value;
1225 if (!fontmap || (ch <= 0xFF && fontmap->glyphs[ch].image) || (ch >= 0xE000 && ch <= 0xE0FF))
1232 map = ft2_oldstyle_map;
1234 //num = (unsigned char) text[i];
1235 //thisw = fnt->width_of[num];
1236 thisw = fnt->width_of[ch];
1237 // FIXME make these smaller to just include the occupied part of the character for slightly faster rendering
1238 if (r_nearest_conchars.integer)
1240 s = (ch & 15)*0.0625f;
1241 t = (ch >> 4)*0.0625f;
1242 u = 0.0625f * thisw;
1247 s = (ch & 15)*0.0625f + (0.5f / tw);
1248 t = (ch >> 4)*0.0625f + (0.5f / th);
1249 u = 0.0625f * thisw - (1.0f / tw);
1250 v = 0.0625f - (1.0f / th);
1252 surf = Mod_Mesh_AddSurface(mod, Mod_Mesh_GetTexture(mod, fnt->pic->name, flags, TEXF_ALPHA | TEXF_CLAMP, MATERIALFLAG_VERTEXCOLOR), true);
1253 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]);
1254 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]);
1255 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]);
1256 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]);
1257 Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
1258 Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
1259 x += width_of[ch] * dw;
1261 if (!map || map == ft2_oldstyle_map || ch < map->start || ch >= map->start + FONT_CHARS_PER_MAP)
1264 map = FontMap_FindForChar(fontmap, ch);
1267 if (!Font_LoadMapForIndex(ft2, map_index, ch, &map))
1274 // this shouldn't happen
1281 mapch = ch - map->start;
1282 thisw = map->glyphs[mapch].advance_x;
1286 if (prevch && Font_GetKerningForMap(ft2, map_index, w, h, prevch, ch, &kx, &ky))
1293 surf = Mod_Mesh_AddSurface(mod, Mod_Mesh_GetTexture(mod, map->pic->name, flags, TEXF_ALPHA | TEXF_CLAMP, MATERIALFLAG_VERTEXCOLOR), true);
1294 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]);
1295 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]);
1296 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]);
1297 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]);
1298 Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
1299 Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
1311 x -= 1.0/pix_x * r_textshadow.value;
1312 y -= 1.0/pix_y * r_textshadow.value;
1318 *outcolor = colorindex;
1320 // note: this relies on the proper text (not shadow) being drawn last
1324 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)
1326 return DrawQ_String_Scale(startx, starty, text, maxlen, w, h, 1, 1, basered, basegreen, baseblue, basealpha, flags, outcolor, ignorecolorcodes, fnt);
1329 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)
1331 return DrawQ_TextWidth_UntilWidth_TrackColors_Scale(text, maxlen, w, h, 1, 1, outcolor, ignorecolorcodes, fnt, maxwidth);
1334 float DrawQ_TextWidth(const char *text, size_t maxlen, float w, float h, qboolean ignorecolorcodes, const dp_font_t *fnt)
1336 return DrawQ_TextWidth_UntilWidth(text, &maxlen, w, h, ignorecolorcodes, fnt, 1000000000);
1339 float DrawQ_TextWidth_UntilWidth(const char *text, size_t *maxlen, float w, float h, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxWidth)
1341 return DrawQ_TextWidth_UntilWidth_TrackColors(text, maxlen, w, h, NULL, ignorecolorcodes, fnt, maxWidth);
1346 // no ^xrgb management
1347 static int DrawQ_BuildColoredText(char *output2c, size_t maxoutchars, const char *text, int maxreadchars, qboolean ignorecolorcodes, int *outcolor)
1349 int color, numchars = 0;
1350 char *outputend2c = output2c + maxoutchars - 2;
1351 if (!outcolor || *outcolor == -1)
1352 color = STRING_COLOR_DEFAULT;
1356 maxreadchars = 1<<30;
1357 textend = text + maxreadchars;
1358 while (text != textend && *text)
1360 if (*text == STRING_COLOR_TAG && !ignorecolorcodes && text + 1 != textend)
1362 if (text[1] == STRING_COLOR_TAG)
1364 else if (text[1] >= '0' && text[1] <= '9')
1366 color = text[1] - '0';
1371 if (output2c >= outputend2c)
1373 *output2c++ = *text++;
1374 *output2c++ = color;
1377 output2c[0] = output2c[1] = 0;
1384 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)
1386 dp_model_t *mod = CL_Mesh_UI();
1390 pic = Draw_CachePic("white");
1391 // make sure pic is loaded - we don't use the texture here, Mod_Mesh_GetTexture looks up the skinframe by name
1392 Draw_GetPicTexture(pic);
1396 height = pic->height;
1397 surf = Mod_Mesh_AddSurface(mod, Mod_Mesh_GetTexture(mod, pic->name, flags, pic->texflags, MATERIALFLAG_VERTEXCOLOR), true);
1398 e0 = Mod_Mesh_IndexForVertex(mod, surf, x , y , 0, 0, 0, -1, s1, t1, 0, 0, r1, g1, b1, a1);
1399 e1 = Mod_Mesh_IndexForVertex(mod, surf, x + width, y , 0, 0, 0, -1, s2, t2, 0, 0, r2, g2, b2, a2);
1400 e2 = Mod_Mesh_IndexForVertex(mod, surf, x + width, y + height, 0, 0, 0, -1, s4, t4, 0, 0, r4, g4, b4, a4);
1401 e3 = Mod_Mesh_IndexForVertex(mod, surf, x , y + height, 0, 0, 0, -1, s3, t3, 0, 0, r3, g3, b3, a3);
1402 Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
1403 Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
1406 void DrawQ_Line (float width, float x1, float y1, float x2, float y2, float r, float g, float b, float alpha, int flags)
1408 dp_model_t *mod = CL_Mesh_UI();
1411 float offsetx, offsety;
1412 // width is measured in real pixels
1413 if (fabs(x2 - x1) > fabs(y2 - y1))
1416 offsety = 0.5f * width * vid_conheight.value / vid.height;
1420 offsetx = 0.5f * width * vid_conwidth.value / vid.width;
1423 surf = Mod_Mesh_AddSurface(mod, Mod_Mesh_GetTexture(mod, "white", 0, 0, MATERIALFLAG_VERTEXCOLOR), true);
1424 e0 = Mod_Mesh_IndexForVertex(mod, surf, x1 - offsetx, y1 - offsety, 10, 0, 0, -1, 0, 0, 0, 0, r, g, b, alpha);
1425 e1 = Mod_Mesh_IndexForVertex(mod, surf, x2 - offsetx, y2 - offsety, 10, 0, 0, -1, 0, 0, 0, 0, r, g, b, alpha);
1426 e2 = Mod_Mesh_IndexForVertex(mod, surf, x2 + offsetx, y2 + offsety, 10, 0, 0, -1, 0, 0, 0, 0, r, g, b, alpha);
1427 e3 = Mod_Mesh_IndexForVertex(mod, surf, x1 + offsetx, y1 + offsety, 10, 0, 0, -1, 0, 0, 0, 0, r, g, b, alpha);
1428 Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
1429 Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
1432 void DrawQ_SetClipArea(float x, float y, float width, float height)
1437 // We have to convert the con coords into real coords
1438 // OGL uses bottom to top (origin is in bottom left)
1439 ix = (int)(0.5 + x * ((float)r_refdef.view.width / vid_conwidth.integer)) + r_refdef.view.x;
1440 iy = (int)(0.5 + y * ((float)r_refdef.view.height / vid_conheight.integer)) + r_refdef.view.y;
1441 iw = (int)(0.5 + width * ((float)r_refdef.view.width / vid_conwidth.integer));
1442 ih = (int)(0.5 + height * ((float)r_refdef.view.height / vid_conheight.integer));
1443 switch(vid.renderpath)
1445 case RENDERPATH_GL20:
1446 case RENDERPATH_GLES2:
1447 GL_Scissor(ix, vid.height - iy - ih, iw, ih);
1451 GL_ScissorTest(true);
1454 void DrawQ_ResetClipArea(void)
1457 GL_ScissorTest(false);
1460 void DrawQ_Finish(void)
1463 r_refdef.draw2dstage = 0;
1466 void DrawQ_RecalcView(void)
1469 if(r_refdef.draw2dstage)
1470 r_refdef.draw2dstage = -1; // next draw call will set viewport etc. again
1473 void DrawQ_FlushUI(void)
1476 dp_model_t *mod = CL_Mesh_UI();
1477 if (mod->num_surfaces == 0)
1480 if (!r_draw2d.integer && !r_draw2d_force)
1482 Mod_Mesh_Reset(mod);
1486 // TODO: render the mesh using R_Q1BSP_Draw or similar, for full material support.
1487 GL_DepthMask(false);
1488 R_Mesh_PrepareVertices_Generic_Arrays(mod->surfmesh.num_vertices, mod->surfmesh.data_vertex3f, mod->surfmesh.data_lightmapcolor4f, mod->surfmesh.data_texcoordtexture2f);
1489 for (i = 0; i < mod->num_surfaces; i++)
1491 msurface_t *surf = mod->data_surfaces + i;
1492 texture_t *tex = surf->texture;
1493 if (tex->currentmaterialflags & MATERIALFLAG_CUSTOMBLEND)
1494 GL_BlendFunc(tex->customblendfunc[0], tex->customblendfunc[1]);
1495 else if (tex->currentmaterialflags & MATERIALFLAG_ADD)
1496 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
1497 else if (tex->currentmaterialflags & MATERIALFLAG_ALPHA)
1498 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1500 GL_BlendFunc(GL_ONE, GL_ZERO);
1501 R_SetupShader_Generic(tex->currentskinframe->base, NULL, GL_MODULATE, 1, (tex->currentmaterialflags & MATERIALFLAG_CUSTOMBLEND) ? false : true, true, false);
1502 R_Mesh_Draw(surf->num_firstvertex, surf->num_vertices, surf->num_firsttriangle, surf->num_triangles, mod->surfmesh.data_element3i, NULL, 0, mod->surfmesh.data_element3s, NULL, 0);
1505 Mod_Mesh_Reset(mod);