]> git.xonotic.org Git - xonotic/darkplaces.git/blob - gl_draw.c
Merge branch 'Mario/wrath-darkplaces' into Mario/wrath-darkplaces_extra
[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_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 qboolean 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 > realtime)
241                 return;
242         nextpurgetime = 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);
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(qboolean 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, qboolean 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_Warnf("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(&cmd_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 qboolean 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         dp_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         dp_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, qboolean 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 // NOTE: this function always draws exactly one character if maxwidth <= 0
890 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)
891 {
892         const char *text_start = text;
893         int colorindex = STRING_COLOR_DEFAULT;
894         size_t i;
895         float x = 0;
896         Uchar ch, mapch, nextch;
897         Uchar prevch = 0; // used for kerning
898         int tempcolorindex;
899         float kx;
900         int map_index = 0;
901         size_t bytes_left;
902         ft2_font_map_t *fontmap = NULL;
903         ft2_font_map_t *map = NULL;
904         //ft2_font_map_t *prevmap = NULL;
905         ft2_font_t *ft2 = fnt->ft2;
906         // float ftbase_x;
907         qboolean snap = true;
908         qboolean least_one = false;
909         float dw; // display w
910         //float dh; // display h
911         const float *width_of;
912
913         if (!h) h = w;
914         if (!h) {
915                 w = h = 1;
916                 snap = false;
917         }
918         // do this in the end
919         w *= fnt->settings.scale;
920         h *= fnt->settings.scale;
921
922         // find the most fitting size:
923         if (ft2 != NULL)
924         {
925                 if (snap)
926                         map_index = Font_IndexForSize(ft2, h, &w, &h);
927                 else
928                         map_index = Font_IndexForSize(ft2, h, NULL, NULL);
929                 fontmap = Font_MapForIndex(ft2, map_index);
930         }
931
932         dw = w * sw;
933         //dh = h * sh;
934
935         if (*maxlen < 1)
936                 *maxlen = 1<<30;
937
938         if (!outcolor || *outcolor == -1)
939                 colorindex = STRING_COLOR_DEFAULT;
940         else
941                 colorindex = *outcolor;
942
943         // maxwidth /= fnt->scale; // w and h are multiplied by it already
944         // ftbase_x = snap_to_pixel_x(0);
945         
946         if(maxwidth <= 0)
947         {
948                 least_one = true;
949                 maxwidth = -maxwidth;
950         }
951
952         //if (snap)
953         //      x = snap_to_pixel_x(x, 0.4); // haha, it's 0 anyway
954
955         if (fontmap)
956                 width_of = fontmap->width_of;
957         else
958                 width_of = fnt->width_of;
959
960         i = 0;
961         while (((bytes_left = *maxlen - (text - text_start)) > 0) && *text)
962         {
963                 size_t i0 = i;
964                 nextch = ch = u8_getnchar(text, &text, bytes_left);
965                 i = text - text_start;
966                 if (!ch)
967                         break;
968                 if (ch == ' ' && !fontmap)
969                 {
970                         if(!least_one || i0) // never skip the first character
971                         if(x + width_of[(int) ' '] * dw > maxwidth)
972                         {
973                                 i = i0;
974                                 break; // oops, can't draw this
975                         }
976                         x += width_of[(int) ' '] * dw;
977                         continue;
978                 }
979                 // i points to the char after ^
980                 if (ch == STRING_COLOR_TAG && !ignorecolorcodes && i < *maxlen)
981                 {
982                         ch = *text; // colors are ascii, so no u8_ needed
983                         if (ch <= '9' && ch >= '0') // ^[0-9] found
984                         {
985                                 colorindex = ch - '0';
986                                 ++text;
987                                 ++i;
988                                 continue;
989                         }
990                         // i points to the char after ^...
991                         // i+3 points to 3 in ^x123
992                         // i+3 == *maxlen would mean that char is missing
993                         else if (ch == STRING_COLOR_RGB_TAG_CHAR && i + 3 < *maxlen ) // ^x found
994                         {
995                                 // building colorindex...
996                                 ch = tolower(text[1]);
997                                 tempcolorindex = 0x10000; // binary: 1,0000,0000,0000,0000
998                                 if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 12;
999                                 else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 12;
1000                                 else tempcolorindex = 0;
1001                                 if (tempcolorindex)
1002                                 {
1003                                         ch = tolower(text[2]);
1004                                         if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 8;
1005                                         else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 8;
1006                                         else tempcolorindex = 0;
1007                                         if (tempcolorindex)
1008                                         {
1009                                                 ch = tolower(text[3]);
1010                                                 if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 4;
1011                                                 else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 4;
1012                                                 else tempcolorindex = 0;
1013                                                 if (tempcolorindex)
1014                                                 {
1015                                                         colorindex = tempcolorindex | 0xf;
1016                                                         // ...done! now colorindex has rgba codes (1,rrrr,gggg,bbbb,aaaa)
1017                                                         i+=4;
1018                                                         text += 4;
1019                                                         continue;
1020                                                 }
1021                                         }
1022                                 }
1023                         }
1024                         else if (ch == STRING_COLOR_TAG) // ^^ found, ignore the first ^ and go to print the second
1025                         {
1026                                 i++;
1027                                 text++;
1028                         }
1029                         i--;
1030                 }
1031                 ch = nextch;
1032
1033                 if (!fontmap || (ch <= 0xFF && fontmap->glyphs[ch].image) || (ch >= 0xE000 && ch <= 0xE0FF))
1034                 {
1035                         if (ch > 0xE000)
1036                                 ch -= 0xE000;
1037                         if (ch > 0xFF)
1038                                 continue;
1039                         if (fontmap)
1040                                 map = ft2_oldstyle_map;
1041                         prevch = 0;
1042                         if(!least_one || i0) // never skip the first character
1043                         if(x + width_of[ch] * dw > maxwidth)
1044                         {
1045                                 i = i0;
1046                                 break; // oops, can't draw this
1047                         }
1048                         x += width_of[ch] * dw;
1049                 } else {
1050                         if (!map || map == ft2_oldstyle_map || ch < map->start || ch >= map->start + FONT_CHARS_PER_MAP)
1051                         {
1052                                 map = FontMap_FindForChar(fontmap, ch);
1053                                 if (!map)
1054                                 {
1055                                         if (!Font_LoadMapForIndex(ft2, map_index, ch, &map))
1056                                                 break;
1057                                         if (!map)
1058                                                 break;
1059                                 }
1060                         }
1061                         mapch = ch - map->start;
1062                         if (prevch && Font_GetKerningForMap(ft2, map_index, w, h, prevch, ch, &kx, NULL))
1063                                 x += kx * dw;
1064                         x += map->glyphs[mapch].advance_x * dw;
1065                         //prevmap = map;
1066                         prevch = ch;
1067                 }
1068         }
1069
1070         *maxlen = i;
1071
1072         if (outcolor)
1073                 *outcolor = colorindex;
1074
1075         return x;
1076 }
1077
1078 float DrawQ_Color[4];
1079 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)
1080 {
1081         int shadow, colorindex = STRING_COLOR_DEFAULT;
1082         size_t i;
1083         float x = startx, y, s, t, u, v, thisw;
1084         Uchar ch, mapch, nextch;
1085         Uchar prevch = 0; // used for kerning
1086         int tempcolorindex;
1087         int map_index = 0;
1088         //ft2_font_map_t *prevmap = NULL; // the previous map
1089         ft2_font_map_t *map = NULL;     // the currently used map
1090         ft2_font_map_t *fontmap = NULL; // the font map for the size
1091         float ftbase_y;
1092         const char *text_start = text;
1093         float kx, ky;
1094         ft2_font_t *ft2 = fnt->ft2;
1095         qboolean snap = true;
1096         float pix_x, pix_y;
1097         size_t bytes_left;
1098         float dw, dh;
1099         const float *width_of;
1100         dp_model_t *mod = CL_Mesh_UI();
1101         msurface_t *surf = NULL;
1102         int e0, e1, e2, e3;
1103         int tw, th;
1104         tw = Draw_GetPicWidth(fnt->pic);
1105         th = Draw_GetPicHeight(fnt->pic);
1106
1107         if (!h) h = w;
1108         if (!h) {
1109                 h = w = 1;
1110                 snap = false;
1111         }
1112
1113         starty -= (fnt->settings.scale - 1) * h * 0.5 - fnt->settings.voffset*h; // center & offset
1114         w *= fnt->settings.scale;
1115         h *= fnt->settings.scale;
1116
1117         if (ft2 != NULL)
1118         {
1119                 if (snap)
1120                         map_index = Font_IndexForSize(ft2, h, &w, &h);
1121                 else
1122                         map_index = Font_IndexForSize(ft2, h, NULL, NULL);
1123                 fontmap = Font_MapForIndex(ft2, map_index);
1124         }
1125
1126         dw = w * sw;
1127         dh = h * sh;
1128
1129         // draw the font at its baseline when using freetype
1130         //ftbase_x = 0;
1131         ftbase_y = dh * (4.5/6.0);
1132
1133         if (maxlen < 1)
1134                 maxlen = 1<<30;
1135
1136         if(!r_draw2d.integer && !r_draw2d_force)
1137                 return startx + DrawQ_TextWidth_UntilWidth_TrackColors_Scale(text, &maxlen, w, h, sw, sh, NULL, ignorecolorcodes, fnt, 1000000000);
1138
1139         //ftbase_x = snap_to_pixel_x(ftbase_x);
1140         if(snap)
1141         {
1142                 startx = snap_to_pixel_x(startx, 0.4);
1143                 starty = snap_to_pixel_y(starty, 0.4);
1144                 ftbase_y = snap_to_pixel_y(ftbase_y, 0.3);
1145         }
1146
1147         pix_x = vid.width / vid_conwidth.value;
1148         pix_y = vid.height / vid_conheight.value;
1149
1150         if (fontmap)
1151                 width_of = fontmap->width_of;
1152         else
1153                 width_of = fnt->width_of;
1154
1155         for (shadow = r_textshadow.value != 0 && basealpha > 0;shadow >= 0;shadow--)
1156         {
1157                 prevch = 0;
1158                 text = text_start;
1159
1160                 if (!outcolor || *outcolor == -1)
1161                         colorindex = STRING_COLOR_DEFAULT;
1162                 else
1163                         colorindex = *outcolor;
1164
1165                 DrawQ_GetTextColor(DrawQ_Color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0);
1166
1167                 x = startx;
1168                 y = starty;
1169                 /*
1170                 if (shadow)
1171                 {
1172                         x += r_textshadow.value * vid.width / vid_conwidth.value;
1173                         y += r_textshadow.value * vid.height / vid_conheight.value;
1174                 }
1175                 */
1176                 while (((bytes_left = maxlen - (text - text_start)) > 0) && *text)
1177                 {
1178                         nextch = ch = u8_getnchar(text, &text, bytes_left);
1179                         i = text - text_start;
1180                         if (!ch)
1181                                 break;
1182                         if (ch == ' ' && !fontmap)
1183                         {
1184                                 x += width_of[(int) ' '] * dw;
1185                                 continue;
1186                         }
1187                         if (ch == STRING_COLOR_TAG && !ignorecolorcodes && i < maxlen)
1188                         {
1189                                 ch = *text; // colors are ascii, so no u8_ needed
1190                                 if (ch <= '9' && ch >= '0') // ^[0-9] found
1191                                 {
1192                                         colorindex = ch - '0';
1193                                         DrawQ_GetTextColor(DrawQ_Color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0);
1194                                         ++text;
1195                                         ++i;
1196                                         continue;
1197                                 }
1198                                 else if (ch == STRING_COLOR_RGB_TAG_CHAR && i+3 < maxlen ) // ^x found
1199                                 {
1200                                         // building colorindex...
1201                                         ch = tolower(text[1]);
1202                                         tempcolorindex = 0x10000; // binary: 1,0000,0000,0000,0000
1203                                         if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 12;
1204                                         else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 12;
1205                                         else tempcolorindex = 0;
1206                                         if (tempcolorindex)
1207                                         {
1208                                                 ch = tolower(text[2]);
1209                                                 if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 8;
1210                                                 else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 8;
1211                                                 else tempcolorindex = 0;
1212                                                 if (tempcolorindex)
1213                                                 {
1214                                                         ch = tolower(text[3]);
1215                                                         if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 4;
1216                                                         else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 4;
1217                                                         else tempcolorindex = 0;
1218                                                         if (tempcolorindex)
1219                                                         {
1220                                                                 colorindex = tempcolorindex | 0xf;
1221                                                                 // ...done! now colorindex has rgba codes (1,rrrr,gggg,bbbb,aaaa)
1222                                                                 //Con_Printf("^1colorindex:^7 %x\n", colorindex);
1223                                                                 DrawQ_GetTextColor(DrawQ_Color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0);
1224                                                                 i+=4;
1225                                                                 text+=4;
1226                                                                 continue;
1227                                                         }
1228                                                 }
1229                                         }
1230                                 }
1231                                 else if (ch == STRING_COLOR_TAG)
1232                                 {
1233                                         i++;
1234                                         text++;
1235                                 }
1236                                 i--;
1237                         }
1238                         // get the backup
1239                         ch = nextch;
1240                         // using a value of -1 for the oldstyle map because NULL means uninitialized...
1241                         // this way we don't need to rebind fnt->tex for every old-style character
1242                         // E000..E0FF: emulate old-font characters (to still have smileys and such available)
1243                         if (shadow)
1244                         {
1245                                 x += 1.0/pix_x * r_textshadow.value;
1246                                 y += 1.0/pix_y * r_textshadow.value;
1247                         }
1248                         if (!fontmap || (ch <= 0xFF && fontmap->glyphs[ch].image) || (ch >= 0xE000 && ch <= 0xE0FF))
1249                         {
1250                                 if (ch >= 0xE000)
1251                                         ch -= 0xE000;
1252                                 if (ch > 0xFF)
1253                                         goto out;
1254                                 if (fontmap)
1255                                         map = ft2_oldstyle_map;
1256                                 prevch = 0;
1257                                 //num = (unsigned char) text[i];
1258                                 //thisw = fnt->width_of[num];
1259                                 thisw = fnt->width_of[ch];
1260                                 // FIXME make these smaller to just include the occupied part of the character for slightly faster rendering
1261                                 if (r_nearest_conchars.integer)
1262                                 {
1263                                         s = (ch & 15)*0.0625f;
1264                                         t = (ch >> 4)*0.0625f;
1265                                         u = 0.0625f * thisw;
1266                                         v = 0.0625f;
1267                                 }
1268                                 else
1269                                 {
1270                                         s = (ch & 15)*0.0625f + (0.5f / tw);
1271                                         t = (ch >> 4)*0.0625f + (0.5f / th);
1272                                         u = 0.0625f * thisw - (1.0f / tw);
1273                                         v = 0.0625f - (1.0f / th);
1274                                 }
1275                                 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);
1276                                 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]);
1277                                 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]);
1278                                 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]);
1279                                 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]);
1280                                 Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
1281                                 Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
1282                                 x += width_of[ch] * dw;
1283                         } else {
1284                                 if (!map || map == ft2_oldstyle_map || ch < map->start || ch >= map->start + FONT_CHARS_PER_MAP)
1285                                 {
1286                                         // find the new map
1287                                         map = FontMap_FindForChar(fontmap, ch);
1288                                         if (!map)
1289                                         {
1290                                                 if (!Font_LoadMapForIndex(ft2, map_index, ch, &map))
1291                                                 {
1292                                                         shadow = -1;
1293                                                         break;
1294                                                 }
1295                                                 if (!map)
1296                                                 {
1297                                                         // this shouldn't happen
1298                                                         shadow = -1;
1299                                                         break;
1300                                                 }
1301                                         }
1302                                 }
1303
1304                                 mapch = ch - map->start;
1305                                 thisw = map->glyphs[mapch].advance_x;
1306
1307                                 //x += ftbase_x;
1308                                 y += ftbase_y;
1309                                 if (prevch && Font_GetKerningForMap(ft2, map_index, w, h, prevch, ch, &kx, &ky))
1310                                 {
1311                                         x += kx * dw;
1312                                         y += ky * dh;
1313                                 }
1314                                 else
1315                                         kx = ky = 0;
1316                                 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);
1317                                 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]);
1318                                 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]);
1319                                 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]);
1320                                 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]);
1321                                 Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
1322                                 Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
1323                                 //x -= ftbase_x;
1324                                 y -= ftbase_y;
1325
1326                                 x += thisw * dw;
1327
1328                                 //prevmap = map;
1329                                 prevch = ch;
1330                         }
1331 out:
1332                         if (shadow)
1333                         {
1334                                 x -= 1.0/pix_x * r_textshadow.value;
1335                                 y -= 1.0/pix_y * r_textshadow.value;
1336                         }
1337                 }
1338         }
1339
1340         if (outcolor)
1341                 *outcolor = colorindex;
1342         
1343         // note: this relies on the proper text (not shadow) being drawn last
1344         return x;
1345 }
1346
1347 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)
1348 {
1349         return DrawQ_String_Scale(startx, starty, text, maxlen, w, h, 1, 1, basered, basegreen, baseblue, basealpha, flags, outcolor, ignorecolorcodes, fnt);
1350 }
1351
1352 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)
1353 {
1354         return DrawQ_TextWidth_UntilWidth_TrackColors_Scale(text, maxlen, w, h, 1, 1, outcolor, ignorecolorcodes, fnt, maxwidth);
1355 }
1356
1357 float DrawQ_TextWidth(const char *text, size_t maxlen, float w, float h, qboolean ignorecolorcodes, const dp_font_t *fnt)
1358 {
1359         return DrawQ_TextWidth_UntilWidth(text, &maxlen, w, h, ignorecolorcodes, fnt, 1000000000);
1360 }
1361
1362 float DrawQ_TextWidth_UntilWidth(const char *text, size_t *maxlen, float w, float h, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxWidth)
1363 {
1364         return DrawQ_TextWidth_UntilWidth_TrackColors(text, maxlen, w, h, NULL, ignorecolorcodes, fnt, maxWidth);
1365 }
1366
1367 #if 0
1368 // not used
1369 // no ^xrgb management
1370 static int DrawQ_BuildColoredText(char *output2c, size_t maxoutchars, const char *text, int maxreadchars, qboolean ignorecolorcodes, int *outcolor)
1371 {
1372         int color, numchars = 0;
1373         char *outputend2c = output2c + maxoutchars - 2;
1374         if (!outcolor || *outcolor == -1)
1375                 color = STRING_COLOR_DEFAULT;
1376         else
1377                 color = *outcolor;
1378         if (!maxreadchars)
1379                 maxreadchars = 1<<30;
1380         textend = text + maxreadchars;
1381         while (text != textend && *text)
1382         {
1383                 if (*text == STRING_COLOR_TAG && !ignorecolorcodes && text + 1 != textend)
1384                 {
1385                         if (text[1] == STRING_COLOR_TAG)
1386                                 text++;
1387                         else if (text[1] >= '0' && text[1] <= '9')
1388                         {
1389                                 color = text[1] - '0';
1390                                 text += 2;
1391                                 continue;
1392                         }
1393                 }
1394                 if (output2c >= outputend2c)
1395                         break;
1396                 *output2c++ = *text++;
1397                 *output2c++ = color;
1398                 numchars++;
1399         }
1400         output2c[0] = output2c[1] = 0;
1401         if (outcolor)
1402                 *outcolor = color;
1403         return numchars;
1404 }
1405 #endif
1406
1407 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)
1408 {
1409         dp_model_t *mod = CL_Mesh_UI();
1410         msurface_t *surf;
1411         int e0, e1, e2, e3;
1412         if (!pic)
1413                 pic = Draw_CachePic("white");
1414         // make sure pic is loaded - we don't use the texture here, Mod_Mesh_GetTexture looks up the skinframe by name
1415         Draw_GetPicTexture(pic);
1416         if (width == 0)
1417                 width = pic->width;
1418         if (height == 0)
1419                 height = pic->height;
1420         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);
1421         e0 = Mod_Mesh_IndexForVertex(mod, surf, x        , y         , 0, 0, 0, -1, s1, t1, 0, 0, r1, g1, b1, a1);
1422         e1 = Mod_Mesh_IndexForVertex(mod, surf, x + width, y         , 0, 0, 0, -1, s2, t2, 0, 0, r2, g2, b2, a2);
1423         e2 = Mod_Mesh_IndexForVertex(mod, surf, x + width, y + height, 0, 0, 0, -1, s4, t4, 0, 0, r4, g4, b4, a4);
1424         e3 = Mod_Mesh_IndexForVertex(mod, surf, x        , y + height, 0, 0, 0, -1, s3, t3, 0, 0, r3, g3, b3, a3);
1425         Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
1426         Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
1427 }
1428
1429 void DrawQ_Line (float width, float x1, float y1, float x2, float y2, float r, float g, float b, float alpha, int flags)
1430 {
1431         dp_model_t *mod = CL_Mesh_UI();
1432         msurface_t *surf;
1433         int e0, e1, e2, e3;
1434         float offsetx, offsety;
1435         // width is measured in real pixels
1436         if (fabs(x2 - x1) > fabs(y2 - y1))
1437         {
1438                 offsetx = 0;
1439                 offsety = 0.5f * width * vid_conheight.value / vid.height;
1440         }
1441         else
1442         {
1443                 offsetx = 0.5f * width * vid_conwidth.value / vid.width;
1444                 offsety = 0;
1445         }
1446         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);
1447         e0 = Mod_Mesh_IndexForVertex(mod, surf, x1 - offsetx, y1 - offsety, 10, 0, 0, -1, 0, 0, 0, 0, r, g, b, alpha);
1448         e1 = Mod_Mesh_IndexForVertex(mod, surf, x2 - offsetx, y2 - offsety, 10, 0, 0, -1, 0, 0, 0, 0, r, g, b, alpha);
1449         e2 = Mod_Mesh_IndexForVertex(mod, surf, x2 + offsetx, y2 + offsety, 10, 0, 0, -1, 0, 0, 0, 0, r, g, b, alpha);
1450         e3 = Mod_Mesh_IndexForVertex(mod, surf, x1 + offsetx, y1 + offsety, 10, 0, 0, -1, 0, 0, 0, 0, r, g, b, alpha);
1451         Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
1452         Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
1453 }
1454
1455 void DrawQ_SetClipArea(float x, float y, float width, float height)
1456 {
1457         int ix, iy, iw, ih;
1458         DrawQ_FlushUI();
1459
1460         // We have to convert the con coords into real coords
1461         // OGL uses bottom to top (origin is in bottom left)
1462         ix = (int)(0.5 + x * ((float)r_refdef.view.width / vid_conwidth.integer)) + r_refdef.view.x;
1463         iy = (int)(0.5 + y * ((float)r_refdef.view.height / vid_conheight.integer)) + r_refdef.view.y;
1464         iw = (int)(0.5 + width * ((float)r_refdef.view.width / vid_conwidth.integer));
1465         ih = (int)(0.5 + height * ((float)r_refdef.view.height / vid_conheight.integer));
1466         switch(vid.renderpath)
1467         {
1468         case RENDERPATH_GL32:
1469         case RENDERPATH_GLES2:
1470                 GL_Scissor(ix, vid.height - iy - ih, iw, ih);
1471                 break;
1472         }
1473
1474         GL_ScissorTest(true);
1475 }
1476
1477 void DrawQ_ResetClipArea(void)
1478 {
1479         DrawQ_FlushUI();
1480         GL_ScissorTest(false);
1481 }
1482
1483 void DrawQ_Finish(void)
1484 {
1485         DrawQ_FlushUI();
1486         r_refdef.draw2dstage = 0;
1487 }
1488
1489 void DrawQ_RecalcView(void)
1490 {
1491         DrawQ_FlushUI();
1492         if(r_refdef.draw2dstage)
1493                 r_refdef.draw2dstage = -1; // next draw call will set viewport etc. again
1494 }
1495
1496 void DrawQ_FlushUI(void)
1497 {
1498         dp_model_t *mod = CL_Mesh_UI();
1499         if (mod->num_surfaces == 0)
1500                 return;
1501
1502         if (!r_draw2d.integer && !r_draw2d_force)
1503         {
1504                 Mod_Mesh_Reset(mod);
1505                 return;
1506         }
1507
1508         // this is roughly equivalent to R_Q1BSP_Draw, so the UI can use full material feature set
1509         r_refdef.view.colorscale = 1;
1510         r_textureframe++; // used only by R_GetCurrentTexture
1511         GL_DepthMask(false);
1512
1513         Mod_Mesh_Finalize(mod);
1514         R_DrawModelSurfaces(&cl_meshentities[MESH_UI].render, false, false, false, false, false, true);
1515
1516         Mod_Mesh_Reset(mod);
1517 }