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