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