]> de.git.xonotic.org Git - xonotic/darkplaces.git/blob - gl_draw.c
uncomment code that prevents memory leak in LoadFont; better English
[xonotic/darkplaces.git] / gl_draw.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3
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.
8
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.
12
13 See the GNU General Public License for more details.
14
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.
18
19 */
20
21 #include "quakedef.h"
22 #include "image.h"
23 #include "wad.h"
24
25 #include "cl_video.h"
26
27 #include "ft2.h"
28 #include "ft2_fontdefs.h"
29
30 struct cachepic_s
31 {
32         // size of pic
33         int width, height;
34         // this flag indicates that it should be loaded and unloaded on demand
35         int autoload;
36         // texture flags to upload with
37         int texflags;
38         // texture may be freed after a while
39         int lastusedframe;
40         // renderable texture
41         skinframe_t *skinframe;
42         // used for hash lookups
43         struct cachepic_s *chain;
44         // flags - CACHEPICFLAG_NEWPIC for example
45         unsigned int flags;
46         // name of pic
47         char name[MAX_QPATH];
48 };
49
50 dp_fonts_t dp_fonts;
51 static mempool_t *fonts_mempool = NULL;
52
53 cvar_t r_textshadow = {CF_CLIENT | CF_ARCHIVE, "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 = {CF_CLIENT | CF_ARCHIVE, "r_textbrightness", "0", "additional brightness for text color codes (0 keeps colors as is, 1 makes them all white)"};
55 cvar_t r_textcontrast = {CF_CLIENT | CF_ARCHIVE, "r_textcontrast", "1", "additional contrast for text color codes (1 keeps colors as is, 0 makes them all black)"};
56
57 cvar_t r_font_postprocess_blur = {CF_CLIENT | CF_ARCHIVE, "r_font_postprocess_blur", "0", "font blur amount"};
58 cvar_t r_font_postprocess_outline = {CF_CLIENT | CF_ARCHIVE, "r_font_postprocess_outline", "0", "font outline amount"};
59 cvar_t r_font_postprocess_shadow_x = {CF_CLIENT | CF_ARCHIVE, "r_font_postprocess_shadow_x", "0", "font shadow X shift amount, applied during outlining"};
60 cvar_t r_font_postprocess_shadow_y = {CF_CLIENT | CF_ARCHIVE, "r_font_postprocess_shadow_y", "0", "font shadow Y shift amount, applied during outlining"};
61 cvar_t r_font_postprocess_shadow_z = {CF_CLIENT | CF_ARCHIVE, "r_font_postprocess_shadow_z", "0", "font shadow Z shift amount, applied during blurring"};
62 cvar_t r_font_hinting = {CF_CLIENT | CF_ARCHIVE, "r_font_hinting", "3", "0 = no hinting, 1 = light autohinting, 2 = full autohinting, 3 = full hinting"};
63 cvar_t r_font_antialias = {CF_CLIENT | CF_ARCHIVE, "r_font_antialias", "1", "0 = monochrome, 1 = grey" /* , 2 = rgb, 3 = bgr" */};
64 cvar_t r_nearest_2d = {CF_CLIENT | CF_ARCHIVE, "r_nearest_2d", "0", "use nearest filtering on all 2d textures (including conchars)"};
65 cvar_t r_nearest_conchars = {CF_CLIENT | CF_ARCHIVE, "r_nearest_conchars", "0", "use nearest filtering on conchars texture"};
66
67 //=============================================================================
68 /* Support Routines */
69
70 static cachepic_t *cachepichash[CACHEPICHASHSIZE];
71 static cachepic_t cachepics[MAX_CACHED_PICS];
72 static int numcachepics;
73
74 rtexturepool_t *drawtexturepool;
75
76 int draw_frame = 1;
77
78 /*
79 ================
80 Draw_CachePic
81 ================
82 */
83 // FIXME: move this to client somehow
84 cachepic_t *Draw_CachePic_Flags(const char *path, unsigned int cachepicflags)
85 {
86         int crc, hashkey;
87         cachepic_t *pic;
88         int texflags;
89
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;
101
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)
106         {
107                 if (!strcmp(path, pic->name))
108                 {
109                         // if it was created (or replaced) by Draw_NewPic, just return it
110                         if (!(pic->flags & CACHEPICFLAG_NEWPIC))
111                         {
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))
115                                 {
116                                         Con_DPrintf("Draw_CachePic(\"%s\"): frame %i: reloading pic due to mismatch on flags\n", path, draw_frame);
117                                         goto reload;
118                                 }
119                                 if (!pic->skinframe || !pic->skinframe->base)
120                                 {
121                                         if (pic->flags & CACHEPICFLAG_FAILONMISSING)
122                                                 return NULL;
123                                         Con_DPrintf("Draw_CachePic(\"%s\"): frame %i: reloading pic\n", path, draw_frame);
124                                         goto reload;
125                                 }
126                                 if (!(cachepicflags & CACHEPICFLAG_NOTPERSISTENT))
127                                         pic->autoload = false; // caller is making this pic persistent
128                         }
129                         if (pic->skinframe)
130                                 R_SkinFrame_MarkUsed(pic->skinframe);
131                         pic->lastusedframe = draw_frame;
132                         return pic;
133                 }
134         }
135
136         if (numcachepics == MAX_CACHED_PICS)
137         {
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
141         }
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));
146         // link into list
147         pic->chain = cachepichash[hashkey];
148         cachepichash[hashkey] = pic;
149
150 reload:
151         if (pic->skinframe)
152                 R_SkinFrame_PurgeSkinFrame(pic->skinframe);
153
154         pic->flags = cachepicflags;
155         pic->texflags = texflags;
156         pic->autoload = (cachepicflags & CACHEPICFLAG_NOTPERSISTENT) != 0;
157         pic->lastusedframe = draw_frame;
158
159         if (pic->skinframe)
160         {
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);
163         }
164         else
165         {
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);
168         }
169
170         // get the dimensions of the image we loaded (if it was successful)
171         if (pic->skinframe && pic->skinframe->base)
172         {
173                 pic->width = R_TextureWidth(pic->skinframe->base);
174                 pic->height = R_TextureHeight(pic->skinframe->base);
175         }
176
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);
179
180         return pic;
181 }
182
183 cachepic_t *Draw_CachePic (const char *path)
184 {
185         return Draw_CachePic_Flags (path, 0); // default to persistent!
186 }
187
188 const char *Draw_GetPicName(cachepic_t *pic)
189 {
190         if (pic == NULL)
191                 return "";
192         return pic->name;
193 }
194
195 int Draw_GetPicWidth(cachepic_t *pic)
196 {
197         if (pic == NULL)
198                 return 0;
199         return pic->width;
200 }
201
202 int Draw_GetPicHeight(cachepic_t *pic)
203 {
204         if (pic == NULL)
205                 return 0;
206         return pic->height;
207 }
208
209 qbool Draw_IsPicLoaded(cachepic_t *pic)
210 {
211         if (pic == NULL)
212                 return false;
213         if (pic->autoload && (!pic->skinframe || !pic->skinframe->base))
214         {
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);
217         }
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;
220 }
221
222 rtexture_t *Draw_GetPicTexture(cachepic_t *pic)
223 {
224         if (pic == NULL)
225                 return NULL;
226         if (pic->autoload && (!pic->skinframe || !pic->skinframe->base))
227         {
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);
230         }
231         pic->lastusedframe = draw_frame;
232         return pic->skinframe ? pic->skinframe->base : NULL;
233 }
234
235 void Draw_Frame(void)
236 {
237         int i;
238         cachepic_t *pic;
239         static double nextpurgetime;
240         if (nextpurgetime > host.realtime)
241                 return;
242         nextpurgetime = host.realtime + 0.05;
243         for (i = 0, pic = cachepics;i < numcachepics;i++, pic++)
244         {
245                 if (pic->autoload && pic->skinframe && pic->skinframe->base && pic->lastusedframe < draw_frame - 3)
246                 {
247                         Con_DPrintf("Draw_Frame(%i): Unloading \"%s\"\n", draw_frame, pic->name);
248                         R_SkinFrame_PurgeSkinFrame(pic->skinframe);
249                 }
250         }
251         draw_frame++;
252 }
253
254 cachepic_t *Draw_NewPic(const char *picname, int width, int height, unsigned char *pixels_bgra, textype_t textype, int texflags)
255 {
256         int crc, hashkey;
257         cachepic_t *pic;
258
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))
263                         break;
264
265         if (pic)
266         {
267                 if (pic->flags & CACHEPICFLAG_NEWPIC && pic->skinframe && pic->skinframe->base && pic->width == width && pic->height == height)
268                 {
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, 0);
271                         R_SkinFrame_MarkUsed(pic->skinframe);
272                         pic->lastusedframe = draw_frame;
273                         return pic;
274                 }
275                 Con_DPrintf("Draw_NewPic(\"%s\"): frame %i: reloading pic because flags/size changed\n", picname, draw_frame);
276         }
277         else
278         {
279                 if (numcachepics == MAX_CACHED_PICS)
280                 {
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
284                 }
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));
289                 // link into list
290                 pic->chain = cachepichash[hashkey];
291                 cachepichash[hashkey] = pic;
292         }
293
294         R_SkinFrame_PurgeSkinFrame(pic->skinframe);
295
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;
300         pic->width = width;
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;
304         return pic;
305 }
306
307 void Draw_FreePic(const char *picname)
308 {
309         int crc;
310         int hashkey;
311         cachepic_t *pic;
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)
316         {
317                 if (!strcmp (picname, pic->name) && pic->skinframe)
318                 {
319                         Con_DPrintf("Draw_FreePic(\"%s\"): frame %i: freeing pic\n", picname, draw_frame);
320                         R_SkinFrame_PurgeSkinFrame(pic->skinframe);
321                         return;
322                 }
323         }
324 }
325
326 static float snap_to_pixel_x(float x, float roundUpAt);
327 extern int con_linewidth; // to force rewrapping
328 void LoadFont(qbool override, const char *name, dp_font_t *fnt, float scale, float voffset)
329 {
330         int i, ch;
331         float maxwidth;
332         char widthfile[MAX_QPATH];
333         char *widthbuf;
334         fs_offset_t widthbufsize;
335
336         if(override || !fnt->texpath[0])
337         {
338                 strlcpy(fnt->texpath, name, sizeof(fnt->texpath));
339                 // load the cvars when the font is FIRST loader
340                 fnt->settings.scale = scale;
341                 fnt->settings.voffset = voffset;
342                 fnt->settings.antialias = r_font_antialias.integer;
343                 fnt->settings.hinting = r_font_hinting.integer;
344                 fnt->settings.outline = r_font_postprocess_outline.value;
345                 fnt->settings.blur = r_font_postprocess_blur.value;
346                 fnt->settings.shadowx = r_font_postprocess_shadow_x.value;
347                 fnt->settings.shadowy = r_font_postprocess_shadow_y.value;
348                 fnt->settings.shadowz = r_font_postprocess_shadow_z.value;
349         }
350         // fix bad scale
351         if (fnt->settings.scale <= 0)
352                 fnt->settings.scale = 1;
353
354         if(drawtexturepool == NULL)
355                 return; // before gl_draw_start, so will be loaded later
356
357         if(fnt->ft2)
358         {
359                 // clear previous freetype font to prevent leaking memory
360                 Font_UnloadFont(fnt->ft2);
361                 Mem_Free(fnt->ft2);
362                 fnt->ft2 = NULL;
363         }
364
365         if(fnt->req_face != -1)
366         {
367                 if(!Font_LoadFont(fnt->texpath, fnt))
368                         Con_DPrintf("Failed to load font-file for '%s', it will not support as many characters.\n", fnt->texpath);
369         }
370
371         fnt->pic = Draw_CachePic_Flags(fnt->texpath, CACHEPICFLAG_QUIET | CACHEPICFLAG_NOCOMPRESSION | (r_nearest_conchars.integer ? CACHEPICFLAG_NEAREST : 0) | CACHEPICFLAG_FAILONMISSING);
372         if(!Draw_IsPicLoaded(fnt->pic))
373         {
374                 for (i = 0; i < MAX_FONT_FALLBACKS; ++i)
375                 {
376                         if (!fnt->fallbacks[i][0])
377                                 break;
378                         fnt->pic = Draw_CachePic_Flags(fnt->fallbacks[i], CACHEPICFLAG_QUIET | CACHEPICFLAG_NOCOMPRESSION | (r_nearest_conchars.integer ? CACHEPICFLAG_NEAREST : 0) | CACHEPICFLAG_FAILONMISSING);
379                         if(Draw_IsPicLoaded(fnt->pic))
380                                 break;
381                 }
382                 if(!Draw_IsPicLoaded(fnt->pic))
383                 {
384                         fnt->pic = Draw_CachePic_Flags("gfx/conchars", CACHEPICFLAG_NOCOMPRESSION | (r_nearest_conchars.integer ? CACHEPICFLAG_NEAREST : 0));
385                         strlcpy(widthfile, "gfx/conchars.width", sizeof(widthfile));
386                 }
387                 else
388                         dpsnprintf(widthfile, sizeof(widthfile), "%s.width", fnt->fallbacks[i]);
389         }
390         else
391                 dpsnprintf(widthfile, sizeof(widthfile), "%s.width", fnt->texpath);
392
393         // unspecified width == 1 (base width)
394         for(ch = 0; ch < 256; ++ch)
395                 fnt->width_of[ch] = 1;
396
397         // FIXME load "name.width", if it fails, fill all with 1
398         if((widthbuf = (char *) FS_LoadFile(widthfile, tempmempool, true, &widthbufsize)))
399         {
400                 float extraspacing = 0;
401                 const char *p = widthbuf;
402
403                 ch = 0;
404                 while(ch < 256)
405                 {
406                         if(!COM_ParseToken_Simple(&p, false, false, true))
407                                 return;
408
409                         switch(*com_token)
410                         {
411                                 case '0':
412                                 case '1':
413                                 case '2':
414                                 case '3':
415                                 case '4':
416                                 case '5':
417                                 case '6':
418                                 case '7':
419                                 case '8':
420                                 case '9':
421                                 case '+':
422                                 case '-':
423                                 case '.':
424                                         fnt->width_of[ch] = atof(com_token) + extraspacing;
425                                         ch++;
426                                         break;
427                                 default:
428                                         if(!strcmp(com_token, "extraspacing"))
429                                         {
430                                                 if(!COM_ParseToken_Simple(&p, false, false, true))
431                                                         return;
432                                                 extraspacing = atof(com_token);
433                                         }
434                                         else if(!strcmp(com_token, "scale"))
435                                         {
436                                                 if(!COM_ParseToken_Simple(&p, false, false, true))
437                                                         return;
438                                                 fnt->settings.scale = atof(com_token);
439                                         }
440                                         else
441                                         {
442                                                 Con_DPrintf("Warning: skipped unknown font property %s\n", com_token);
443                                                 if(!COM_ParseToken_Simple(&p, false, false, true))
444                                                         return;
445                                         }
446                                         break;
447                         }
448                 }
449
450                 Mem_Free(widthbuf);
451         }
452
453         if(fnt->ft2)
454         {
455                 for (i = 0; i < MAX_FONT_SIZES; ++i)
456                 {
457                         ft2_font_map_t *map = Font_MapForIndex(fnt->ft2, i);
458                         if (!map)
459                                 break;
460                         for(ch = 0; ch < 256; ++ch)
461                                 fnt->width_of_ft2[i][ch] = Font_SnapTo(fnt->width_of[ch], 1/map->size);
462                 }
463         }
464
465         maxwidth = fnt->width_of[0];
466         for(i = 1; i < 256; ++i)
467                 maxwidth = max(maxwidth, fnt->width_of[i]);
468         fnt->maxwidth = maxwidth;
469
470         // fix up maxwidth for overlap
471         fnt->maxwidth *= fnt->settings.scale;
472
473         if(fnt == FONT_CONSOLE)
474                 con_linewidth = -1; // rewrap console in next frame
475 }
476
477 extern cvar_t developer_font;
478 dp_font_t *FindFont(const char *title, qbool allocate_new)
479 {
480         int i, oldsize;
481
482         // find font
483         for(i = 0; i < dp_fonts.maxsize; ++i)
484                 if(!strcmp(dp_fonts.f[i].title, title))
485                         return &dp_fonts.f[i];
486         // if not found - try allocate
487         if (allocate_new)
488         {
489                 // find any font with empty title
490                 for(i = 0; i < dp_fonts.maxsize; ++i)
491                 {
492                         if(!strcmp(dp_fonts.f[i].title, ""))
493                         {
494                                 strlcpy(dp_fonts.f[i].title, title, sizeof(dp_fonts.f[i].title));
495                                 return &dp_fonts.f[i];
496                         }
497                 }
498                 // if no any 'free' fonts - expand buffer
499                 oldsize = dp_fonts.maxsize;
500                 dp_fonts.maxsize = dp_fonts.maxsize + FONTS_EXPAND;
501                 if (developer_font.integer)
502                         Con_Printf("FindFont: enlarging fonts buffer (%i -> %i)\n", oldsize, dp_fonts.maxsize);
503                 dp_fonts.f = (dp_font_t *)Mem_Realloc(fonts_mempool, dp_fonts.f, sizeof(dp_font_t) * dp_fonts.maxsize);
504                 // relink ft2 structures
505                 for(i = 0; i < oldsize; ++i)
506                         if (dp_fonts.f[i].ft2)
507                                 dp_fonts.f[i].ft2->settings = &dp_fonts.f[i].settings;
508                 // register a font in first expanded slot
509                 strlcpy(dp_fonts.f[oldsize].title, title, sizeof(dp_fonts.f[oldsize].title));
510                 return &dp_fonts.f[oldsize];
511         }
512         return NULL;
513 }
514
515 static float snap_to_pixel_x(float x, float roundUpAt)
516 {
517         float pixelpos = x * vid.width / vid_conwidth.value;
518         int snap = (int) pixelpos;
519         if (pixelpos - snap >= roundUpAt) ++snap;
520         return ((float)snap * vid_conwidth.value / vid.width);
521         /*
522         x = (int)(x * vid.width / vid_conwidth.value);
523         x = (x * vid_conwidth.value / vid.width);
524         return x;
525         */
526 }
527
528 static float snap_to_pixel_y(float y, float roundUpAt)
529 {
530         float pixelpos = y * vid.height / vid_conheight.value;
531         int snap = (int) pixelpos;
532         if (pixelpos - snap > roundUpAt) ++snap;
533         return ((float)snap * vid_conheight.value / vid.height);
534         /*
535         y = (int)(y * vid.height / vid_conheight.value);
536         y = (y * vid_conheight.value / vid.height);
537         return y;
538         */
539 }
540
541 static void LoadFont_f(cmd_state_t *cmd)
542 {
543         dp_font_t *f;
544         int i, sizes;
545         const char *filelist, *c, *cm;
546         float sz, scale, voffset;
547         char mainfont[MAX_QPATH];
548
549         if(Cmd_Argc(cmd) < 2)
550         {
551                 Con_Printf("Available font commands:\n");
552                 for(i = 0; i < dp_fonts.maxsize; ++i)
553                         if (dp_fonts.f[i].title[0])
554                                 Con_Printf("  loadfont %s gfx/tgafile[...] [custom switches] [sizes...]\n", dp_fonts.f[i].title);
555                 Con_Printf("A font can simply be gfx/tgafile, or alternatively you\n"
556                            "can specify multiple fonts and faces\n"
557                            "Like this: gfx/vera-sans:2,gfx/fallback:1\n"
558                            "to load face 2 of the font gfx/vera-sans and use face 1\n"
559                            "of gfx/fallback as fallback font.\n"
560                            "You can also specify a list of font sizes to load, like this:\n"
561                            "loadfont console gfx/conchars,gfx/fallback 8 12 16 24 32\n"
562                            "In many cases, 8 12 16 24 32 should be a good choice.\n"
563                            "custom switches:\n"
564                            " scale x : scale all characters by this amount when rendering (doesnt change line height)\n"
565                            " voffset x : offset all chars vertical when rendering, this is multiplied to character height\n"
566                         );
567                 return;
568         }
569         f = FindFont(Cmd_Argv(cmd, 1), true);
570         if(f == NULL)
571         {
572                 Con_Printf("font function not found\n");
573                 return;
574         }
575
576         if(Cmd_Argc(cmd) < 3)
577                 filelist = "gfx/conchars";
578         else
579                 filelist = Cmd_Argv(cmd, 2);
580
581         memset(f->fallbacks, 0, sizeof(f->fallbacks));
582         memset(f->fallback_faces, 0, sizeof(f->fallback_faces));
583
584         // first font is handled "normally"
585         c = strchr(filelist, ':');
586         cm = strchr(filelist, ',');
587         if(c && (!cm || c < cm))
588                 f->req_face = atoi(c+1);
589         else
590         {
591                 f->req_face = 0;
592                 c = cm;
593         }
594
595         if(!c || (c - filelist) >= MAX_QPATH)
596                 strlcpy(mainfont, filelist, sizeof(mainfont));
597         else
598         {
599                 memcpy(mainfont, filelist, c - filelist);
600                 mainfont[c - filelist] = 0;
601         }
602
603         for(i = 0; i < MAX_FONT_FALLBACKS; ++i)
604         {
605                 c = strchr(filelist, ',');
606                 if(!c)
607                         break;
608                 filelist = c + 1;
609                 if(!*filelist)
610                         break;
611                 c = strchr(filelist, ':');
612                 cm = strchr(filelist, ',');
613                 if(c && (!cm || c < cm))
614                         f->fallback_faces[i] = atoi(c+1);
615                 else
616                 {
617                         f->fallback_faces[i] = 0; // f->req_face; could make it stick to the default-font's face index
618                         c = cm;
619                 }
620                 if(!c || (c-filelist) >= MAX_QPATH)
621                 {
622                         strlcpy(f->fallbacks[i], filelist, sizeof(mainfont));
623                 }
624                 else
625                 {
626                         memcpy(f->fallbacks[i], filelist, c - filelist);
627                         f->fallbacks[i][c - filelist] = 0;
628                 }
629         }
630
631         // for now: by default load only one size: the default size
632         f->req_sizes[0] = 0;
633         for(i = 1; i < MAX_FONT_SIZES; ++i)
634                 f->req_sizes[i] = -1;
635
636         scale = 1;
637         voffset = 0;
638         if(Cmd_Argc(cmd) >= 4)
639         {
640                 for(sizes = 0, i = 3; i < Cmd_Argc(cmd); ++i)
641                 {
642                         // special switches
643                         if (!strcmp(Cmd_Argv(cmd, i), "scale"))
644                         {
645                                 i++;
646                                 if (i < Cmd_Argc(cmd))
647                                         scale = atof(Cmd_Argv(cmd, i));
648                                 continue;
649                         }
650                         if (!strcmp(Cmd_Argv(cmd, i), "voffset"))
651                         {
652                                 i++;
653                                 if (i < Cmd_Argc(cmd))
654                                         voffset = atof(Cmd_Argv(cmd, i));
655                                 continue;
656                         }
657
658                         if (sizes == -1)
659                                 continue; // no slot for other sizes
660
661                         // parse one of sizes
662                         sz = atof(Cmd_Argv(cmd, i));
663                         if (sz > 0.001f && sz < 1000.0f) // do not use crap sizes
664                         {
665                                 // search for duplicated sizes
666                                 int j;
667                                 for (j=0; j<sizes; j++)
668                                         if (f->req_sizes[j] == sz)
669                                                 break;
670                                 if (j != sizes)
671                                         continue; // sz already in req_sizes, don't add it again
672
673                                 if (sizes == MAX_FONT_SIZES)
674                                 {
675                                         Con_Printf(CON_WARN "Warning: specified more than %i different font sizes, exceding ones are ignored\n", MAX_FONT_SIZES);
676                                         sizes = -1;
677                                         continue;
678                                 }
679                                 f->req_sizes[sizes] = sz;
680                                 sizes++;
681                         }
682                 }
683         }
684
685         LoadFont(true, mainfont, f, scale, voffset);
686 }
687
688 /*
689 ===============
690 Draw_Init
691 ===============
692 */
693 static void gl_draw_start(void)
694 {
695         int i;
696         char vabuf[1024];
697         drawtexturepool = R_AllocTexturePool();
698
699         numcachepics = 0;
700         memset(cachepichash, 0, sizeof(cachepichash));
701
702         font_start();
703
704         // load default font textures
705         for(i = 0; i < dp_fonts.maxsize; ++i)
706                 if (dp_fonts.f[i].title[0])
707                         LoadFont(false, va(vabuf, sizeof(vabuf), "gfx/font_%s", dp_fonts.f[i].title), &dp_fonts.f[i], 1, 0);
708 }
709
710 static void gl_draw_shutdown(void)
711 {
712         font_shutdown();
713
714         R_FreeTexturePool(&drawtexturepool);
715
716         numcachepics = 0;
717         memset(cachepichash, 0, sizeof(cachepichash));
718 }
719
720 static void gl_draw_newmap(void)
721 {
722         int i;
723         font_newmap();
724
725         // mark all of the persistent pics so they are not purged...
726         for (i = 0; i < numcachepics; i++)
727         {
728                 cachepic_t *pic = cachepics + i;
729                 if (!pic->autoload && pic->skinframe)
730                         R_SkinFrame_MarkUsed(pic->skinframe);
731         }
732 }
733
734 void GL_Draw_Init (void)
735 {
736         int i, j;
737
738         Cvar_RegisterVariable(&r_font_postprocess_blur);
739         Cvar_RegisterVariable(&r_font_postprocess_outline);
740         Cvar_RegisterVariable(&r_font_postprocess_shadow_x);
741         Cvar_RegisterVariable(&r_font_postprocess_shadow_y);
742         Cvar_RegisterVariable(&r_font_postprocess_shadow_z);
743         Cvar_RegisterVariable(&r_font_hinting);
744         Cvar_RegisterVariable(&r_font_antialias);
745         Cvar_RegisterVariable(&r_textshadow);
746         Cvar_RegisterVariable(&r_textbrightness);
747         Cvar_RegisterVariable(&r_textcontrast);
748         Cvar_RegisterVariable(&r_nearest_2d);
749         Cvar_RegisterVariable(&r_nearest_conchars);
750
751         // allocate fonts storage
752         fonts_mempool = Mem_AllocPool("FONTS", 0, NULL);
753         dp_fonts.maxsize = MAX_FONTS;
754         dp_fonts.f = (dp_font_t *)Mem_Alloc(fonts_mempool, sizeof(dp_font_t) * dp_fonts.maxsize);
755         memset(dp_fonts.f, 0, sizeof(dp_font_t) * dp_fonts.maxsize);
756
757         // assign starting font names
758         strlcpy(FONT_DEFAULT->title, "default", sizeof(FONT_DEFAULT->title));
759         strlcpy(FONT_DEFAULT->texpath, "gfx/conchars", sizeof(FONT_DEFAULT->texpath));
760         strlcpy(FONT_CONSOLE->title, "console", sizeof(FONT_CONSOLE->title));
761         strlcpy(FONT_SBAR->title, "sbar", sizeof(FONT_SBAR->title));
762         strlcpy(FONT_NOTIFY->title, "notify", sizeof(FONT_NOTIFY->title));
763         strlcpy(FONT_CHAT->title, "chat", sizeof(FONT_CHAT->title));
764         strlcpy(FONT_CENTERPRINT->title, "centerprint", sizeof(FONT_CENTERPRINT->title));
765         strlcpy(FONT_INFOBAR->title, "infobar", sizeof(FONT_INFOBAR->title));
766         strlcpy(FONT_MENU->title, "menu", sizeof(FONT_MENU->title));
767         for(i = 0, j = 0; i < MAX_USERFONTS; ++i)
768                 if(!FONT_USER(i)->title[0])
769                         dpsnprintf(FONT_USER(i)->title, sizeof(FONT_USER(i)->title), "user%d", j++);
770
771         Cmd_AddCommand(CF_CLIENT, "loadfont", LoadFont_f, "loadfont function tganame loads a font; example: loadfont console gfx/veramono; loadfont without arguments lists the available functions");
772         R_RegisterModule("GL_Draw", gl_draw_start, gl_draw_shutdown, gl_draw_newmap, NULL, NULL);
773 }
774
775 void DrawQ_Start(void)
776 {
777         r_refdef.draw2dstage = 1;
778         R_ResetViewRendering2D_Common(0, NULL, NULL, 0, 0, vid.width, vid.height, vid_conwidth.integer, vid_conheight.integer);
779 }
780
781 qbool r_draw2d_force = false;
782
783 void DrawQ_Pic(float x, float y, cachepic_t *pic, float width, float height, float red, float green, float blue, float alpha, int flags)
784 {
785         model_t *mod = CL_Mesh_UI();
786         msurface_t *surf;
787         int e0, e1, e2, e3;
788         if (!pic)
789                 pic = Draw_CachePic("white");
790         // make sure pic is loaded - we don't use the texture here, Mod_Mesh_GetTexture looks up the skinframe by name
791         Draw_GetPicTexture(pic);
792         if (width == 0)
793                 width = pic->width;
794         if (height == 0)
795                 height = pic->height;
796         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);
797         e0 = Mod_Mesh_IndexForVertex(mod, surf, x        , y         , 0, 0, 0, -1, 0, 0, 0, 0, red, green, blue, alpha);
798         e1 = Mod_Mesh_IndexForVertex(mod, surf, x + width, y         , 0, 0, 0, -1, 1, 0, 0, 0, red, green, blue, alpha);
799         e2 = Mod_Mesh_IndexForVertex(mod, surf, x + width, y + height, 0, 0, 0, -1, 1, 1, 0, 0, red, green, blue, alpha);
800         e3 = Mod_Mesh_IndexForVertex(mod, surf, x        , y + height, 0, 0, 0, -1, 0, 1, 0, 0, red, green, blue, alpha);
801         Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
802         Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
803 }
804
805 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)
806 {
807         float af = DEG2RAD(-angle); // forward
808         float ar = DEG2RAD(-angle + 90); // right
809         float sinaf = sin(af);
810         float cosaf = cos(af);
811         float sinar = sin(ar);
812         float cosar = cos(ar);
813         model_t *mod = CL_Mesh_UI();
814         msurface_t *surf;
815         int e0, e1, e2, e3;
816         if (!pic)
817                 pic = Draw_CachePic("white");
818         // make sure pic is loaded - we don't use the texture here, Mod_Mesh_GetTexture looks up the skinframe by name
819         Draw_GetPicTexture(pic);
820         if (width == 0)
821                 width = pic->width;
822         if (height == 0)
823                 height = pic->height;
824         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);
825         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);
826         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);
827         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);
828         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);
829         Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
830         Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
831 }
832
833 void DrawQ_Fill(float x, float y, float width, float height, float red, float green, float blue, float alpha, int flags)
834 {
835         DrawQ_Pic(x, y, Draw_CachePic("white"), width, height, red, green, blue, alpha, flags);
836 }
837
838 /// color tag printing
839 static const vec4_t string_colors[] =
840 {
841         // Quake3 colors
842         // LadyHavoc: why on earth is cyan before magenta in Quake3?
843         // LadyHavoc: note: Doom3 uses white for [0] and [7]
844         {0.0, 0.0, 0.0, 1.0}, // black
845         {1.0, 0.0, 0.0, 1.0}, // red
846         {0.0, 1.0, 0.0, 1.0}, // green
847         {1.0, 1.0, 0.0, 1.0}, // yellow
848         {0.0, 0.0, 1.0, 1.0}, // blue
849         {0.0, 1.0, 1.0, 1.0}, // cyan
850         {1.0, 0.0, 1.0, 1.0}, // magenta
851         {1.0, 1.0, 1.0, 1.0}, // white
852         // [515]'s BX_COLOREDTEXT extension
853         {1.0, 1.0, 1.0, 0.5}, // half transparent
854         {0.5, 0.5, 0.5, 1.0}  // half brightness
855         // Black's color table
856         //{1.0, 1.0, 1.0, 1.0},
857         //{1.0, 0.0, 0.0, 1.0},
858         //{0.0, 1.0, 0.0, 1.0},
859         //{0.0, 0.0, 1.0, 1.0},
860         //{1.0, 1.0, 0.0, 1.0},
861         //{0.0, 1.0, 1.0, 1.0},
862         //{1.0, 0.0, 1.0, 1.0},
863         //{0.1, 0.1, 0.1, 1.0}
864 };
865
866 #define STRING_COLORS_COUNT     (sizeof(string_colors) / sizeof(vec4_t))
867
868 static void DrawQ_GetTextColor(float color[4], int colorindex, float r, float g, float b, float a, qbool shadow)
869 {
870         float C = r_textcontrast.value;
871         float B = r_textbrightness.value;
872         if (colorindex & 0x10000) // that bit means RGB color
873         {
874                 color[0] = ((colorindex >> 12) & 0xf) / 15.0;
875                 color[1] = ((colorindex >> 8) & 0xf) / 15.0;
876                 color[2] = ((colorindex >> 4) & 0xf) / 15.0;
877                 color[3] = (colorindex & 0xf) / 15.0;
878         }
879         else
880                 Vector4Copy(string_colors[colorindex], color);
881         Vector4Set(color, color[0] * r * C + B, color[1] * g * C + B, color[2] * b * C + B, color[3] * a);
882         if (shadow)
883         {
884                 float shadowalpha = (color[0]+color[1]+color[2]) * 0.8;
885                 Vector4Set(color, 0, 0, 0, color[3] * bound(0, shadowalpha, 1));
886         }
887 }
888
889 // returns a colorindex (format 0x1RGBA) if str is a valid RGB string
890 // returns 0 otherwise
891 static int RGBstring_to_colorindex(const char *str)
892 {
893         Uchar ch; 
894         int ind = 0x0001 << 4;
895         do {
896                 if (*str <= '9' && *str >= '0')
897                         ind |= (*str - '0');
898                 else
899                 {
900                         ch = tolower(*str);
901                         if (ch >= 'a' && ch <= 'f')
902                                 ind |= (ch - 87);
903                         else
904                                 return 0;
905                 }
906                 ++str;
907                 ind <<= 4;
908         } while(!(ind & 0x10000));
909         return ind | 0xf; // add costant alpha value
910 }
911
912 // NOTE: this function always draws exactly one character if maxwidth <= 0
913 float DrawQ_TextWidth_UntilWidth_TrackColors_Scale(const char *text, size_t *maxlen, float w, float h, float sw, float sh, int *outcolor, qbool ignorecolorcodes, const dp_font_t *fnt, float maxwidth)
914 {
915         const char *text_start = text;
916         int colorindex;
917         size_t i;
918         float x = 0;
919         Uchar ch, mapch, nextch;
920         Uchar prevch = 0; // used for kerning
921         float kx;
922         int map_index = 0;
923         size_t bytes_left;
924         ft2_font_map_t *fontmap = NULL;
925         ft2_font_map_t *map = NULL;
926         ft2_font_t *ft2 = fnt->ft2;
927         // float ftbase_x;
928         qbool snap = true;
929         qbool least_one = false;
930         float dw; // display w
931         //float dh; // display h
932         const float *width_of;
933
934         if (!h) h = w;
935         if (!h) {
936                 w = h = 1;
937                 snap = false;
938         }
939         // do this in the end
940         w *= fnt->settings.scale;
941         h *= fnt->settings.scale;
942
943         // find the most fitting size:
944         if (ft2 != NULL)
945         {
946                 if (snap)
947                         map_index = Font_IndexForSize(ft2, h, &w, &h);
948                 else
949                         map_index = Font_IndexForSize(ft2, h, NULL, NULL);
950                 fontmap = Font_MapForIndex(ft2, map_index);
951         }
952
953         dw = w * sw;
954         //dh = h * sh;
955
956         if (*maxlen < 1)
957                 *maxlen = 1<<30;
958
959         if (!outcolor || *outcolor == -1)
960                 colorindex = STRING_COLOR_DEFAULT;
961         else
962                 colorindex = *outcolor;
963
964         // maxwidth /= fnt->scale; // w and h are multiplied by it already
965         // ftbase_x = snap_to_pixel_x(0);
966
967         if(maxwidth <= 0)
968         {
969                 least_one = true;
970                 maxwidth = -maxwidth;
971         }
972
973         //if (snap)
974         //      x = snap_to_pixel_x(x, 0.4); // haha, it's 0 anyway
975
976         if (fontmap)
977                 width_of = fnt->width_of_ft2[map_index];
978         else
979                 width_of = fnt->width_of;
980
981         i = 0;
982         while (((bytes_left = *maxlen - (text - text_start)) > 0) && *text)
983         {
984                 size_t i0 = i;
985                 nextch = ch = u8_getnchar(text, &text, bytes_left);
986                 i = text - text_start;
987                 if (!ch)
988                         break;
989                 if (ch == ' ' && !fontmap)
990                 {
991                         if(!least_one || i0) // never skip the first character
992                                 if(x + width_of[(int) ' '] * dw > maxwidth)
993                                 {
994                                         i = i0;
995                                         break; // oops, can't draw this
996                                 }
997                         x += width_of[(int) ' '] * dw;
998                         continue;
999                 }
1000                 if (ch == STRING_COLOR_TAG && !ignorecolorcodes && i < *maxlen)
1001                 {
1002                         ch = *text; // colors are ascii, so no u8_ needed
1003                         if (ch <= '9' && ch >= '0') // ^[0-9] found
1004                         {
1005                                 colorindex = ch - '0';
1006                                 ++text;
1007                                 ++i;
1008                                 continue;
1009                         }
1010                         else if (ch == STRING_COLOR_RGB_TAG_CHAR && i + 3 < *maxlen ) // ^x found
1011                         {
1012                                 const char *text_p = &text[1];
1013                                 int tempcolorindex = RGBstring_to_colorindex(text_p);
1014                                 if (tempcolorindex)
1015                                 {
1016                                         colorindex = tempcolorindex;
1017                                         i+=4;
1018                                         text += 4;
1019                                         continue;
1020                                 }
1021                         }
1022                         else if (ch == STRING_COLOR_TAG) // ^^ found
1023                         {
1024                                 i++;
1025                                 text++;
1026                         }
1027                         i--;
1028                 }
1029                 ch = nextch;
1030
1031                 if (!fontmap || (ch <= 0xFF && fontmap->glyphs[ch].image) || (ch >= 0xE000 && ch <= 0xE0FF))
1032                 {
1033                         if (ch > 0xE000)
1034                                 ch -= 0xE000;
1035                         if (ch > 0xFF)
1036                                 continue;
1037                         if (fontmap)
1038                                 map = ft2_oldstyle_map;
1039                         prevch = 0;
1040                         if(!least_one || i0) // never skip the first character
1041                                 if(x + width_of[ch] * dw > maxwidth)
1042                                 {
1043                                         i = i0;
1044                                         break; // oops, can't draw this
1045                                 }
1046                         x += width_of[ch] * dw;
1047                 } else {
1048                         if (!map || map == ft2_oldstyle_map || ch != prevch)
1049                         {
1050                                 Font_GetMapForChar(ft2, map_index, ch, &map, &mapch);
1051                                 if (!map)
1052                                         break;
1053                         }
1054                         if (prevch && Font_GetKerningForMap(ft2, map_index, w, h, prevch, ch, &kx, NULL))
1055                                 x += kx * dw;
1056                         x += map->glyphs[mapch].advance_x * dw;
1057                         prevch = ch;
1058                 }
1059         }
1060
1061         *maxlen = i;
1062
1063         if (outcolor)
1064                 *outcolor = colorindex;
1065
1066         return x;
1067 }
1068
1069 float DrawQ_Color[4];
1070 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, qbool ignorecolorcodes, const dp_font_t *fnt)
1071 {
1072         int shadow, colorindex = STRING_COLOR_DEFAULT;
1073         size_t i;
1074         float x = startx, y, s, t, u, v, thisw;
1075         Uchar ch, mapch, nextch;
1076         Uchar prevch = 0; // used for kerning
1077         int map_index = 0;
1078         ft2_font_map_t *map = NULL;     // the currently used map
1079         ft2_font_map_t *fontmap = NULL; // the font map for the size
1080         float ftbase_y;
1081         const char *text_start = text;
1082         float kx, ky;
1083         ft2_font_t *ft2 = fnt->ft2;
1084         qbool snap = true;
1085         float pix_x, pix_y;
1086         size_t bytes_left;
1087         float dw, dh;
1088         const float *width_of;
1089         model_t *mod = CL_Mesh_UI();
1090         msurface_t *surf = NULL;
1091         int e0, e1, e2, e3;
1092         int tw, th;
1093         tw = Draw_GetPicWidth(fnt->pic);
1094         th = Draw_GetPicHeight(fnt->pic);
1095
1096         if (!h) h = w;
1097         if (!h) {
1098                 h = w = 1;
1099                 snap = false;
1100         }
1101
1102         starty -= (fnt->settings.scale - 1) * h * 0.5 - fnt->settings.voffset*h; // center & offset
1103         w *= fnt->settings.scale;
1104         h *= fnt->settings.scale;
1105
1106         if (ft2 != NULL)
1107         {
1108                 if (snap)
1109                         map_index = Font_IndexForSize(ft2, h, &w, &h);
1110                 else
1111                         map_index = Font_IndexForSize(ft2, h, NULL, NULL);
1112                 fontmap = Font_MapForIndex(ft2, map_index);
1113         }
1114
1115         dw = w * sw;
1116         dh = h * sh;
1117
1118         // draw the font at its baseline when using freetype
1119         //ftbase_x = 0;
1120         ftbase_y = dh * (4.5/6.0);
1121
1122         if (maxlen < 1)
1123                 maxlen = 1<<30;
1124
1125         if(!r_draw2d.integer && !r_draw2d_force)
1126                 return startx + DrawQ_TextWidth_UntilWidth_TrackColors_Scale(text, &maxlen, w, h, sw, sh, NULL, ignorecolorcodes, fnt, 1000000000);
1127
1128         //ftbase_x = snap_to_pixel_x(ftbase_x);
1129         if(snap)
1130         {
1131                 startx = snap_to_pixel_x(startx, 0.4);
1132                 starty = snap_to_pixel_y(starty, 0.4);
1133                 ftbase_y = snap_to_pixel_y(ftbase_y, 0.3);
1134         }
1135
1136         pix_x = vid.width / vid_conwidth.value;
1137         pix_y = vid.height / vid_conheight.value;
1138
1139         if (fontmap)
1140                 width_of = fnt->width_of_ft2[map_index];
1141         else
1142                 width_of = fnt->width_of;
1143
1144         for (shadow = r_textshadow.value != 0 && basealpha > 0;shadow >= 0;shadow--)
1145         {
1146                 prevch = 0;
1147                 text = text_start;
1148
1149                 if (!outcolor || *outcolor == -1)
1150                         colorindex = STRING_COLOR_DEFAULT;
1151                 else
1152                         colorindex = *outcolor;
1153
1154                 DrawQ_GetTextColor(DrawQ_Color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0);
1155
1156                 x = startx;
1157                 y = starty;
1158                 /*
1159                 if (shadow)
1160                 {
1161                         x += r_textshadow.value * vid.width / vid_conwidth.value;
1162                         y += r_textshadow.value * vid.height / vid_conheight.value;
1163                 }
1164                 */
1165                 while (((bytes_left = maxlen - (text - text_start)) > 0) && *text)
1166                 {
1167                         nextch = ch = u8_getnchar(text, &text, bytes_left);
1168                         i = text - text_start;
1169                         if (!ch)
1170                                 break;
1171                         if (ch == ' ' && !fontmap)
1172                         {
1173                                 x += width_of[(int) ' '] * dw;
1174                                 continue;
1175                         }
1176                         if (ch == STRING_COLOR_TAG && !ignorecolorcodes && i < maxlen)
1177                         {
1178                                 ch = *text; // colors are ascii, so no u8_ needed
1179                                 if (ch <= '9' && ch >= '0') // ^[0-9] found
1180                                 {
1181                                         colorindex = ch - '0';
1182                                         DrawQ_GetTextColor(DrawQ_Color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0);
1183                                         ++text;
1184                                         ++i;
1185                                         continue;
1186                                 }
1187                                 else if (ch == STRING_COLOR_RGB_TAG_CHAR && i+3 < maxlen ) // ^x found
1188                                 {
1189                                         const char *text_p = &text[1];
1190                                         int tempcolorindex = RGBstring_to_colorindex(text_p);
1191                                         if(tempcolorindex)
1192                                         {
1193                                                 colorindex = tempcolorindex;
1194                                                 DrawQ_GetTextColor(DrawQ_Color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0);
1195                                                 i+=4;
1196                                                 text+=4;
1197                                                 continue;
1198                                         }
1199                                 }
1200                                 else if (ch == STRING_COLOR_TAG)
1201                                 {
1202                                         i++;
1203                                         text++;
1204                                 }
1205                                 i--;
1206                         }
1207                         // get the backup
1208                         ch = nextch;
1209                         // using a value of -1 for the oldstyle map because NULL means uninitialized...
1210                         // this way we don't need to rebind fnt->tex for every old-style character
1211                         // E000..E0FF: emulate old-font characters (to still have smileys and such available)
1212                         if (shadow)
1213                         {
1214                                 x += 1.0/pix_x * r_textshadow.value;
1215                                 y += 1.0/pix_y * r_textshadow.value;
1216                         }
1217                         if (!fontmap || (ch <= 0xFF && fontmap->glyphs[ch].image) || (ch >= 0xE000 && ch <= 0xE0FF))
1218                         {
1219                                 if (ch >= 0xE000)
1220                                         ch -= 0xE000;
1221                                 if (ch > 0xFF)
1222                                         goto out;
1223                                 if (fontmap)
1224                                         map = ft2_oldstyle_map;
1225                                 prevch = 0;
1226                                 //num = (unsigned char) text[i];
1227                                 //thisw = fnt->width_of[num];
1228                                 thisw = fnt->width_of[ch];
1229                                 // FIXME make these smaller to just include the occupied part of the character for slightly faster rendering
1230                                 if (r_nearest_conchars.integer)
1231                                 {
1232                                         s = (ch & 15)*0.0625f;
1233                                         t = (ch >> 4)*0.0625f;
1234                                         u = 0.0625f * thisw;
1235                                         v = 0.0625f;
1236                                 }
1237                                 else
1238                                 {
1239                                         s = (ch & 15)*0.0625f + (0.5f / tw);
1240                                         t = (ch >> 4)*0.0625f + (0.5f / th);
1241                                         u = 0.0625f * thisw - (1.0f / tw);
1242                                         v = 0.0625f - (1.0f / th);
1243                                 }
1244                                 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);
1245                                 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]);
1246                                 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]);
1247                                 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]);
1248                                 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]);
1249                                 Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
1250                                 Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
1251                                 x += width_of[ch] * dw;
1252                         } else {
1253                                 if (!map || map == ft2_oldstyle_map || ch != prevch)
1254                                 {
1255                                         Font_GetMapForChar(ft2, map_index, ch, &map, &mapch);
1256                                         if (!map)
1257                                         {
1258                                                 shadow = -1;
1259                                                 break;
1260                                         }
1261                                 }
1262
1263                                 thisw = map->glyphs[mapch].advance_x;
1264
1265                                 //x += ftbase_x;
1266                                 y += ftbase_y;
1267                                 if (prevch && Font_GetKerningForMap(ft2, map_index, w, h, prevch, ch, &kx, &ky))
1268                                 {
1269                                         x += kx * dw;
1270                                         y += ky * dh;
1271                                 }
1272                                 else
1273                                         kx = ky = 0;
1274                                 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);
1275                                 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]);
1276                                 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]);
1277                                 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]);
1278                                 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]);
1279                                 Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
1280                                 Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
1281                                 //x -= ftbase_x;
1282                                 y -= ftbase_y;
1283
1284                                 x += thisw * dw;
1285
1286                                 //prevmap = map;
1287                                 prevch = ch;
1288                         }
1289 out:
1290                         if (shadow)
1291                         {
1292                                 x -= 1.0/pix_x * r_textshadow.value;
1293                                 y -= 1.0/pix_y * r_textshadow.value;
1294                         }
1295                 }
1296         }
1297
1298         if (outcolor)
1299                 *outcolor = colorindex;
1300
1301         // note: this relies on the proper text (not shadow) being drawn last
1302         return x;
1303 }
1304
1305 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, qbool ignorecolorcodes, const dp_font_t *fnt)
1306 {
1307         return DrawQ_String_Scale(startx, starty, text, maxlen, w, h, 1, 1, basered, basegreen, baseblue, basealpha, flags, outcolor, ignorecolorcodes, fnt);
1308 }
1309
1310 float DrawQ_TextWidth_UntilWidth_TrackColors(const char *text, size_t *maxlen, float w, float h, int *outcolor, qbool ignorecolorcodes, const dp_font_t *fnt, float maxwidth)
1311 {
1312         return DrawQ_TextWidth_UntilWidth_TrackColors_Scale(text, maxlen, w, h, 1, 1, outcolor, ignorecolorcodes, fnt, maxwidth);
1313 }
1314
1315 float DrawQ_TextWidth(const char *text, size_t maxlen, float w, float h, qbool ignorecolorcodes, const dp_font_t *fnt)
1316 {
1317         return DrawQ_TextWidth_UntilWidth(text, &maxlen, w, h, ignorecolorcodes, fnt, 1000000000);
1318 }
1319
1320 float DrawQ_TextWidth_UntilWidth(const char *text, size_t *maxlen, float w, float h, qbool ignorecolorcodes, const dp_font_t *fnt, float maxWidth)
1321 {
1322         return DrawQ_TextWidth_UntilWidth_TrackColors(text, maxlen, w, h, NULL, ignorecolorcodes, fnt, maxWidth);
1323 }
1324
1325 #if 0
1326 // not used
1327 // no ^xrgb management
1328 static int DrawQ_BuildColoredText(char *output2c, size_t maxoutchars, const char *text, int maxreadchars, qbool ignorecolorcodes, int *outcolor)
1329 {
1330         int color, numchars = 0;
1331         char *outputend2c = output2c + maxoutchars - 2;
1332         if (!outcolor || *outcolor == -1)
1333                 color = STRING_COLOR_DEFAULT;
1334         else
1335                 color = *outcolor;
1336         if (!maxreadchars)
1337                 maxreadchars = 1<<30;
1338         textend = text + maxreadchars;
1339         while (text != textend && *text)
1340         {
1341                 if (*text == STRING_COLOR_TAG && !ignorecolorcodes && text + 1 != textend)
1342                 {
1343                         if (text[1] == STRING_COLOR_TAG)
1344                                 text++;
1345                         else if (text[1] >= '0' && text[1] <= '9')
1346                         {
1347                                 color = text[1] - '0';
1348                                 text += 2;
1349                                 continue;
1350                         }
1351                 }
1352                 if (output2c >= outputend2c)
1353                         break;
1354                 *output2c++ = *text++;
1355                 *output2c++ = color;
1356                 numchars++;
1357         }
1358         output2c[0] = output2c[1] = 0;
1359         if (outcolor)
1360                 *outcolor = color;
1361         return numchars;
1362 }
1363 #endif
1364
1365 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)
1366 {
1367         model_t *mod = CL_Mesh_UI();
1368         msurface_t *surf;
1369         int e0, e1, e2, e3;
1370         if (!pic)
1371                 pic = Draw_CachePic("white");
1372         // make sure pic is loaded - we don't use the texture here, Mod_Mesh_GetTexture looks up the skinframe by name
1373         Draw_GetPicTexture(pic);
1374         if (width == 0)
1375                 width = pic->width;
1376         if (height == 0)
1377                 height = pic->height;
1378         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);
1379         e0 = Mod_Mesh_IndexForVertex(mod, surf, x        , y         , 0, 0, 0, -1, s1, t1, 0, 0, r1, g1, b1, a1);
1380         e1 = Mod_Mesh_IndexForVertex(mod, surf, x + width, y         , 0, 0, 0, -1, s2, t2, 0, 0, r2, g2, b2, a2);
1381         e2 = Mod_Mesh_IndexForVertex(mod, surf, x + width, y + height, 0, 0, 0, -1, s4, t4, 0, 0, r4, g4, b4, a4);
1382         e3 = Mod_Mesh_IndexForVertex(mod, surf, x        , y + height, 0, 0, 0, -1, s3, t3, 0, 0, r3, g3, b3, a3);
1383         Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
1384         Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
1385 }
1386
1387 void DrawQ_Line (float width, float x1, float y1, float x2, float y2, float r, float g, float b, float alpha, int flags)
1388 {
1389         model_t *mod = CL_Mesh_UI();
1390         msurface_t *surf;
1391         int e0, e1, e2, e3;
1392         float offsetx, offsety;
1393         // width is measured in real pixels
1394         if (fabs(x2 - x1) > fabs(y2 - y1))
1395         {
1396                 offsetx = 0;
1397                 offsety = 0.5f * width * vid_conheight.value / vid.height;
1398         }
1399         else
1400         {
1401                 offsetx = 0.5f * width * vid_conwidth.value / vid.width;
1402                 offsety = 0;
1403         }
1404         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);
1405         e0 = Mod_Mesh_IndexForVertex(mod, surf, x1 - offsetx, y1 - offsety, 10, 0, 0, -1, 0, 0, 0, 0, r, g, b, alpha);
1406         e1 = Mod_Mesh_IndexForVertex(mod, surf, x2 - offsetx, y2 - offsety, 10, 0, 0, -1, 0, 0, 0, 0, r, g, b, alpha);
1407         e2 = Mod_Mesh_IndexForVertex(mod, surf, x2 + offsetx, y2 + offsety, 10, 0, 0, -1, 0, 0, 0, 0, r, g, b, alpha);
1408         e3 = Mod_Mesh_IndexForVertex(mod, surf, x1 + offsetx, y1 + offsety, 10, 0, 0, -1, 0, 0, 0, 0, r, g, b, alpha);
1409         Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
1410         Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
1411 }
1412
1413 void DrawQ_SetClipArea(float x, float y, float width, float height)
1414 {
1415         int ix, iy, iw, ih;
1416         DrawQ_FlushUI();
1417
1418         // We have to convert the con coords into real coords
1419         // OGL uses bottom to top (origin is in bottom left)
1420         ix = (int)(0.5 + x * ((float)r_refdef.view.width / vid_conwidth.integer)) + r_refdef.view.x;
1421         iy = (int)(0.5 + y * ((float)r_refdef.view.height / vid_conheight.integer)) + r_refdef.view.y;
1422         iw = (int)(0.5 + width * ((float)r_refdef.view.width / vid_conwidth.integer));
1423         ih = (int)(0.5 + height * ((float)r_refdef.view.height / vid_conheight.integer));
1424         switch(vid.renderpath)
1425         {
1426         case RENDERPATH_GL32:
1427         case RENDERPATH_GLES2:
1428                 GL_Scissor(ix, vid.height - iy - ih, iw, ih);
1429                 break;
1430         }
1431
1432         GL_ScissorTest(true);
1433 }
1434
1435 void DrawQ_ResetClipArea(void)
1436 {
1437         DrawQ_FlushUI();
1438         GL_ScissorTest(false);
1439 }
1440
1441 void DrawQ_Finish(void)
1442 {
1443         DrawQ_FlushUI();
1444         r_refdef.draw2dstage = 0;
1445 }
1446
1447 void DrawQ_RecalcView(void)
1448 {
1449         DrawQ_FlushUI();
1450         if(r_refdef.draw2dstage)
1451                 r_refdef.draw2dstage = -1; // next draw call will set viewport etc. again
1452 }
1453
1454 void DrawQ_FlushUI(void)
1455 {
1456         model_t *mod = CL_Mesh_UI();
1457         if (mod->num_surfaces == 0)
1458                 return;
1459
1460         if (!r_draw2d.integer && !r_draw2d_force)
1461         {
1462                 Mod_Mesh_Reset(mod);
1463                 return;
1464         }
1465
1466         // this is roughly equivalent to R_Mod_Draw, so the UI can use full material feature set
1467         r_refdef.view.colorscale = 1;
1468         r_textureframe++; // used only by R_GetCurrentTexture
1469         GL_DepthMask(false);
1470
1471         Mod_Mesh_Finalize(mod);
1472         R_DrawModelSurfaces(&cl_meshentities[MESH_UI].render, false, false, false, false, false, true);
1473
1474         Mod_Mesh_Reset(mod);
1475 }