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