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