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