]> de.git.xonotic.org Git - xonotic/darkplaces.git/blob - gl_draw.c
Merge PR 'Use the text from modinfo.txt as the mod menu entry'
[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         dp_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                 dp_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                 dp_strlcpy(fnt->texpath, name, sizeof(fnt->texpath));
340                 // load the cvars when the font is FIRST loader
341                 fnt->settings.scale = scale;
342                 fnt->settings.voffset = voffset;
343                 fnt->settings.antialias = r_font_antialias.integer;
344                 fnt->settings.hinting = r_font_hinting.integer;
345                 fnt->settings.outline = r_font_postprocess_outline.value;
346                 fnt->settings.blur = r_font_postprocess_blur.value;
347                 fnt->settings.shadowx = r_font_postprocess_shadow_x.value;
348                 fnt->settings.shadowy = r_font_postprocess_shadow_y.value;
349                 fnt->settings.shadowz = r_font_postprocess_shadow_z.value;
350         }
351
352         // fix bad scale
353         if (fnt->settings.scale <= 0)
354                 fnt->settings.scale = 1;
355
356         if(drawtexturepool == NULL)
357                 return; // before gl_draw_start, so will be loaded later
358
359         if(fnt->ft2)
360         {
361                 // we are going to reload. clear old ft2 data
362                 Font_UnloadFont(fnt->ft2);
363                 Mem_Free(fnt->ft2);
364                 fnt->ft2 = NULL;
365         }
366
367         if(fnt->req_face != -1)
368         {
369                 if(!Font_LoadFont(fnt->texpath, fnt))
370                         Con_DPrintf("Failed to load font-file for '%s', it will not support as many characters.\n", fnt->texpath);
371         }
372
373         fnt->pic = Draw_CachePic_Flags(fnt->texpath, CACHEPICFLAG_QUIET | CACHEPICFLAG_NOCOMPRESSION | (r_nearest_conchars.integer ? CACHEPICFLAG_NEAREST : 0) | CACHEPICFLAG_FAILONMISSING);
374         if(!Draw_IsPicLoaded(fnt->pic))
375         {
376                 for (i = 0; i < MAX_FONT_FALLBACKS; ++i)
377                 {
378                         if (!fnt->fallbacks[i][0])
379                                 break;
380                         fnt->pic = Draw_CachePic_Flags(fnt->fallbacks[i], CACHEPICFLAG_QUIET | CACHEPICFLAG_NOCOMPRESSION | (r_nearest_conchars.integer ? CACHEPICFLAG_NEAREST : 0) | CACHEPICFLAG_FAILONMISSING);
381                         if(Draw_IsPicLoaded(fnt->pic))
382                                 break;
383                 }
384                 if(!Draw_IsPicLoaded(fnt->pic))
385                 {
386                         fnt->pic = Draw_CachePic_Flags("gfx/conchars", CACHEPICFLAG_NOCOMPRESSION | (r_nearest_conchars.integer ? CACHEPICFLAG_NEAREST : 0));
387                         dp_strlcpy(widthfile, "gfx/conchars.width", sizeof(widthfile));
388                 }
389                 else
390                         dpsnprintf(widthfile, sizeof(widthfile), "%s.width", fnt->fallbacks[i]);
391         }
392         else
393                 dpsnprintf(widthfile, sizeof(widthfile), "%s.width", fnt->texpath);
394
395         // unspecified width == 1 (base width)
396         for(ch = 0; ch < 256; ++ch)
397                 fnt->width_of[ch] = 1;
398
399         // FIXME load "name.width", if it fails, fill all with 1
400         if((widthbuf = (char *) FS_LoadFile(widthfile, tempmempool, true, &widthbufsize)))
401         {
402                 float extraspacing = 0;
403                 const char *p = widthbuf;
404
405                 ch = 0;
406                 while(ch < 256)
407                 {
408                         if(!COM_ParseToken_Simple(&p, false, false, true))
409                                 return;
410
411                         switch(*com_token)
412                         {
413                                 case '0':
414                                 case '1':
415                                 case '2':
416                                 case '3':
417                                 case '4':
418                                 case '5':
419                                 case '6':
420                                 case '7':
421                                 case '8':
422                                 case '9':
423                                 case '+':
424                                 case '-':
425                                 case '.':
426                                         fnt->width_of[ch] = atof(com_token) + extraspacing;
427                                         ch++;
428                                         break;
429                                 default:
430                                         if(!strcmp(com_token, "extraspacing"))
431                                         {
432                                                 if(!COM_ParseToken_Simple(&p, false, false, true))
433                                                         return;
434                                                 extraspacing = atof(com_token);
435                                         }
436                                         else if(!strcmp(com_token, "scale"))
437                                         {
438                                                 if(!COM_ParseToken_Simple(&p, false, false, true))
439                                                         return;
440                                                 fnt->settings.scale = atof(com_token);
441                                         }
442                                         else
443                                         {
444                                                 Con_DPrintf("Warning: skipped unknown font property %s\n", com_token);
445                                                 if(!COM_ParseToken_Simple(&p, false, false, true))
446                                                         return;
447                                         }
448                                         break;
449                         }
450                 }
451
452                 Mem_Free(widthbuf);
453         }
454
455         if(fnt->ft2)
456         {
457                 for (i = 0; i < MAX_FONT_SIZES; ++i)
458                 {
459                         ft2_font_map_t *map = Font_MapForIndex(fnt->ft2, i);
460                         if (!map)
461                                 break;
462                         for(ch = 0; ch < 256; ++ch)
463                                 fnt->width_of_ft2[i][ch] = Font_SnapTo(fnt->width_of[ch], 1/map->size);
464                 }
465         }
466
467         maxwidth = fnt->width_of[0];
468         for(i = 1; i < 256; ++i)
469                 maxwidth = max(maxwidth, fnt->width_of[i]);
470         fnt->maxwidth = maxwidth;
471
472         // fix up maxwidth for overlap
473         fnt->maxwidth *= fnt->settings.scale;
474
475         if(fnt == FONT_CONSOLE)
476                 con_linewidth = -1; // rewrap console in next frame
477 }
478
479 extern cvar_t developer_font;
480 dp_font_t *FindFont(const char *title, qbool allocate_new)
481 {
482         int i, oldsize;
483
484         // find font
485         for(i = 0; i < dp_fonts.maxsize; ++i)
486                 if(!strcmp(dp_fonts.f[i].title, title))
487                         return &dp_fonts.f[i];
488         // if not found - try allocate
489         if (allocate_new)
490         {
491                 // find any font with empty title
492                 for(i = 0; i < dp_fonts.maxsize; ++i)
493                 {
494                         if(!strcmp(dp_fonts.f[i].title, ""))
495                         {
496                                 dp_strlcpy(dp_fonts.f[i].title, title, sizeof(dp_fonts.f[i].title));
497                                 return &dp_fonts.f[i];
498                         }
499                 }
500                 // if no any 'free' fonts - expand buffer
501                 oldsize = dp_fonts.maxsize;
502                 dp_fonts.maxsize = dp_fonts.maxsize + FONTS_EXPAND;
503                 if (developer_font.integer)
504                         Con_Printf("FindFont: enlarging fonts buffer (%i -> %i)\n", oldsize, dp_fonts.maxsize);
505                 dp_fonts.f = (dp_font_t *)Mem_Realloc(fonts_mempool, dp_fonts.f, sizeof(dp_font_t) * dp_fonts.maxsize);
506                 // relink ft2 structures
507                 for(i = 0; i < oldsize; ++i)
508                         if (dp_fonts.f[i].ft2)
509                                 dp_fonts.f[i].ft2->settings = &dp_fonts.f[i].settings;
510                 // register a font in first expanded slot
511                 dp_strlcpy(dp_fonts.f[oldsize].title, title, sizeof(dp_fonts.f[oldsize].title));
512                 return &dp_fonts.f[oldsize];
513         }
514         return NULL;
515 }
516
517 static float snap_to_pixel_x(float x, float roundUpAt)
518 {
519         float pixelpos = x * vid.mode.width / vid_conwidth.value;
520         int snap = (int) pixelpos;
521         if (pixelpos - snap >= roundUpAt) ++snap;
522         return ((float)snap * vid_conwidth.value / vid.mode.width);
523         /*
524         x = (int)(x * vid.width / vid_conwidth.value);
525         x = (x * vid_conwidth.value / vid.width);
526         return x;
527         */
528 }
529
530 static float snap_to_pixel_y(float y, float roundUpAt)
531 {
532         float pixelpos = y * vid.mode.height / vid_conheight.value;
533         int snap = (int) pixelpos;
534         if (pixelpos - snap > roundUpAt) ++snap;
535         return ((float)snap * vid_conheight.value / vid.mode.height);
536         /*
537         y = (int)(y * vid.height / vid_conheight.value);
538         y = (y * vid_conheight.value / vid.height);
539         return y;
540         */
541 }
542
543 static void LoadFont_f(cmd_state_t *cmd)
544 {
545         dp_font_t *f;
546         int i, sizes;
547         const char *filelist, *c, *cm;
548         float sz, scale, voffset;
549         char mainfont[MAX_QPATH];
550
551         if(Cmd_Argc(cmd) < 2)
552         {
553                 Con_Printf("Available font commands:\n");
554                 for(i = 0; i < dp_fonts.maxsize; ++i)
555                         if (dp_fonts.f[i].title[0])
556                                 Con_Printf("  loadfont %s gfx/tgafile[...] [custom switches] [sizes...]\n", dp_fonts.f[i].title);
557                 Con_Printf("A font can simply be gfx/tgafile, or alternatively you\n"
558                            "can specify multiple fonts and faces\n"
559                            "Like this: gfx/vera-sans:2,gfx/fallback:1\n"
560                            "to load face 2 of the font gfx/vera-sans and use face 1\n"
561                            "of gfx/fallback as fallback font.\n"
562                            "You can also specify a list of font sizes to load, like this:\n"
563                            "loadfont console gfx/conchars,gfx/fallback 8 12 16 24 32\n"
564                            "In many cases, 8 12 16 24 32 should be a good choice.\n"
565                            "custom switches:\n"
566                            " scale x : scale all characters by this amount when rendering (doesnt change line height)\n"
567                            " voffset x : offset all chars vertical when rendering, this is multiplied to character height\n"
568                         );
569                 return;
570         }
571         f = FindFont(Cmd_Argv(cmd, 1), true);
572         if(f == NULL)
573         {
574                 Con_Printf("font function not found\n");
575                 return;
576         }
577         else
578         {
579                 if (strcmp(cmd->cmdline, f->cmdline) != 0 || r_font_always_reload.integer)
580                         dp_strlcpy(f->cmdline, cmd->cmdline, MAX_FONT_CMDLINE);
581                 else
582                 {
583                         Con_DPrintf("LoadFont: font %s is unchanged\n", Cmd_Argv(cmd, 1));
584                         return;
585                 }
586         }
587
588         if(Cmd_Argc(cmd) < 3)
589                 filelist = "gfx/conchars";
590         else
591                 filelist = Cmd_Argv(cmd, 2);
592
593         memset(f->fallbacks, 0, sizeof(f->fallbacks));
594         memset(f->fallback_faces, 0, sizeof(f->fallback_faces));
595
596         // first font is handled "normally"
597         c = strchr(filelist, ':');
598         cm = strchr(filelist, ',');
599         if(c && (!cm || c < cm))
600                 f->req_face = atoi(c+1);
601         else
602         {
603                 f->req_face = 0;
604                 c = cm;
605         }
606
607         if(!c || (c - filelist) >= MAX_QPATH)
608                 dp_strlcpy(mainfont, filelist, sizeof(mainfont));
609         else
610         {
611                 memcpy(mainfont, filelist, c - filelist);
612                 mainfont[c - filelist] = 0;
613         }
614
615         for(i = 0; i < MAX_FONT_FALLBACKS; ++i)
616         {
617                 c = strchr(filelist, ',');
618                 if(!c)
619                         break;
620                 filelist = c + 1;
621                 if(!*filelist)
622                         break;
623                 c = strchr(filelist, ':');
624                 cm = strchr(filelist, ',');
625                 if(c && (!cm || c < cm))
626                         f->fallback_faces[i] = atoi(c+1);
627                 else
628                 {
629                         f->fallback_faces[i] = 0; // f->req_face; could make it stick to the default-font's face index
630                         c = cm;
631                 }
632                 if(!c || (c-filelist) >= MAX_QPATH)
633                 {
634                         dp_strlcpy(f->fallbacks[i], filelist, sizeof(mainfont));
635                 }
636                 else
637                 {
638                         memcpy(f->fallbacks[i], filelist, c - filelist);
639                         f->fallbacks[i][c - filelist] = 0;
640                 }
641         }
642
643         // for now: by default load only one size: the default size
644         f->req_sizes[0] = 0;
645         for(i = 1; i < MAX_FONT_SIZES; ++i)
646                 f->req_sizes[i] = -1;
647
648         scale = 1;
649         voffset = 0;
650         if(Cmd_Argc(cmd) >= 4)
651         {
652                 for(sizes = 0, i = 3; i < Cmd_Argc(cmd); ++i)
653                 {
654                         // special switches
655                         if (!strcmp(Cmd_Argv(cmd, i), "scale"))
656                         {
657                                 i++;
658                                 if (i < Cmd_Argc(cmd))
659                                         scale = atof(Cmd_Argv(cmd, i));
660                                 continue;
661                         }
662                         if (!strcmp(Cmd_Argv(cmd, i), "voffset"))
663                         {
664                                 i++;
665                                 if (i < Cmd_Argc(cmd))
666                                         voffset = atof(Cmd_Argv(cmd, i));
667                                 continue;
668                         }
669
670                         if (sizes == -1)
671                                 continue; // no slot for other sizes
672
673                         // parse one of sizes
674                         sz = atof(Cmd_Argv(cmd, i));
675                         if (sz > 0.001f && sz < 1000.0f) // do not use crap sizes
676                         {
677                                 // search for duplicated sizes
678                                 int j;
679                                 for (j=0; j<sizes; j++)
680                                         if (f->req_sizes[j] == sz)
681                                                 break;
682                                 if (j != sizes)
683                                         continue; // sz already in req_sizes, don't add it again
684
685                                 if (sizes == MAX_FONT_SIZES)
686                                 {
687                                         Con_Printf(CON_WARN "Warning: specified more than %i different font sizes, exceding ones are ignored\n", MAX_FONT_SIZES);
688                                         sizes = -1;
689                                         continue;
690                                 }
691                                 f->req_sizes[sizes] = sz;
692                                 sizes++;
693                         }
694                 }
695         }
696
697         LoadFont(true, mainfont, f, scale, voffset);
698 }
699
700 /*
701 ===============
702 Draw_Init
703 ===============
704 */
705 static void gl_draw_start(void)
706 {
707         int i;
708         char vabuf[1024];
709         drawtexturepool = R_AllocTexturePool();
710
711         numcachepics = 0;
712         memset(cachepichash, 0, sizeof(cachepichash));
713
714         font_start();
715
716         // load default font textures
717         for(i = 0; i < dp_fonts.maxsize; ++i)
718                 if (dp_fonts.f[i].title[0])
719                         LoadFont(false, va(vabuf, sizeof(vabuf), "gfx/font_%s", dp_fonts.f[i].title), &dp_fonts.f[i], 1, 0);
720 }
721
722 static void gl_draw_shutdown(void)
723 {
724         font_shutdown();
725
726         R_FreeTexturePool(&drawtexturepool);
727
728         numcachepics = 0;
729         memset(cachepichash, 0, sizeof(cachepichash));
730 }
731
732 static void gl_draw_newmap(void)
733 {
734         int i;
735         font_newmap();
736
737         // mark all of the persistent pics so they are not purged...
738         for (i = 0; i < numcachepics; i++)
739         {
740                 cachepic_t *pic = cachepics + i;
741                 if (!pic->autoload && pic->skinframe)
742                         R_SkinFrame_MarkUsed(pic->skinframe);
743         }
744 }
745
746 void GL_Draw_Init (void)
747 {
748         int i, j;
749
750         Cvar_RegisterVariable(&r_font_postprocess_blur);
751         Cvar_RegisterVariable(&r_font_postprocess_outline);
752         Cvar_RegisterVariable(&r_font_postprocess_shadow_x);
753         Cvar_RegisterVariable(&r_font_postprocess_shadow_y);
754         Cvar_RegisterVariable(&r_font_postprocess_shadow_z);
755         Cvar_RegisterVariable(&r_font_hinting);
756         Cvar_RegisterVariable(&r_font_antialias);
757         Cvar_RegisterVariable(&r_font_always_reload);
758         Cvar_RegisterVariable(&r_textshadow);
759         Cvar_RegisterVariable(&r_textbrightness);
760         Cvar_RegisterVariable(&r_textcontrast);
761         Cvar_RegisterVariable(&r_nearest_2d);
762         Cvar_RegisterVariable(&r_nearest_conchars);
763
764         // allocate fonts storage
765         fonts_mempool = Mem_AllocPool("FONTS", 0, NULL);
766         dp_fonts.maxsize = MAX_FONTS;
767         dp_fonts.f = (dp_font_t *)Mem_Alloc(fonts_mempool, sizeof(dp_font_t) * dp_fonts.maxsize);
768         memset(dp_fonts.f, 0, sizeof(dp_font_t) * dp_fonts.maxsize);
769
770         // assign starting font names
771         dp_strlcpy(FONT_DEFAULT->title, "default", sizeof(FONT_DEFAULT->title));
772         dp_strlcpy(FONT_DEFAULT->texpath, "gfx/conchars", sizeof(FONT_DEFAULT->texpath));
773         dp_strlcpy(FONT_CONSOLE->title, "console", sizeof(FONT_CONSOLE->title));
774         dp_strlcpy(FONT_SBAR->title, "sbar", sizeof(FONT_SBAR->title));
775         dp_strlcpy(FONT_NOTIFY->title, "notify", sizeof(FONT_NOTIFY->title));
776         dp_strlcpy(FONT_CHAT->title, "chat", sizeof(FONT_CHAT->title));
777         dp_strlcpy(FONT_CENTERPRINT->title, "centerprint", sizeof(FONT_CENTERPRINT->title));
778         dp_strlcpy(FONT_INFOBAR->title, "infobar", sizeof(FONT_INFOBAR->title));
779         dp_strlcpy(FONT_MENU->title, "menu", sizeof(FONT_MENU->title));
780         for(i = 0, j = 0; i < MAX_USERFONTS; ++i)
781                 if(!FONT_USER(i)->title[0])
782                         dpsnprintf(FONT_USER(i)->title, sizeof(FONT_USER(i)->title), "user%d", j++);
783
784         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");
785         R_RegisterModule("GL_Draw", gl_draw_start, gl_draw_shutdown, gl_draw_newmap, NULL, NULL);
786 }
787
788 void DrawQ_Start(void)
789 {
790         r_refdef.draw2dstage = 1;
791         R_ResetViewRendering2D_Common(0, NULL, NULL, 0, 0, vid.mode.width, vid.mode.height, vid_conwidth.integer, vid_conheight.integer);
792 }
793
794 qbool r_draw2d_force = false;
795
796 void DrawQ_Pic(float x, float y, cachepic_t *pic, float width, float height, float red, float green, float blue, float alpha, int flags)
797 {
798         model_t *mod = CL_Mesh_UI();
799         msurface_t *surf;
800         int e0, e1, e2, e3;
801         if (!pic)
802                 pic = Draw_CachePic("white");
803         // make sure pic is loaded - we don't use the texture here, Mod_Mesh_GetTexture looks up the skinframe by name
804         Draw_GetPicTexture(pic);
805         if (width == 0)
806                 width = pic->width;
807         if (height == 0)
808                 height = pic->height;
809         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);
810         e0 = Mod_Mesh_IndexForVertex(mod, surf, x        , y         , 0, 0, 0, -1, 0, 0, 0, 0, red, green, blue, alpha);
811         e1 = Mod_Mesh_IndexForVertex(mod, surf, x + width, y         , 0, 0, 0, -1, 1, 0, 0, 0, red, green, blue, alpha);
812         e2 = Mod_Mesh_IndexForVertex(mod, surf, x + width, y + height, 0, 0, 0, -1, 1, 1, 0, 0, red, green, blue, alpha);
813         e3 = Mod_Mesh_IndexForVertex(mod, surf, x        , y + height, 0, 0, 0, -1, 0, 1, 0, 0, red, green, blue, alpha);
814         Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
815         Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
816 }
817
818 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)
819 {
820         float af = DEG2RAD(-angle); // forward
821         float ar = DEG2RAD(-angle + 90); // right
822         float sinaf = sin(af);
823         float cosaf = cos(af);
824         float sinar = sin(ar);
825         float cosar = cos(ar);
826         model_t *mod = CL_Mesh_UI();
827         msurface_t *surf;
828         int e0, e1, e2, e3;
829         if (!pic)
830                 pic = Draw_CachePic("white");
831         // make sure pic is loaded - we don't use the texture here, Mod_Mesh_GetTexture looks up the skinframe by name
832         Draw_GetPicTexture(pic);
833         if (width == 0)
834                 width = pic->width;
835         if (height == 0)
836                 height = pic->height;
837         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);
838         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);
839         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);
840         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);
841         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);
842         Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
843         Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
844 }
845
846 void DrawQ_Fill(float x, float y, float width, float height, float red, float green, float blue, float alpha, int flags)
847 {
848         DrawQ_Pic(x, y, Draw_CachePic("white"), width, height, red, green, blue, alpha, flags);
849 }
850
851 /// color tag printing
852 static const vec4_t string_colors[] =
853 {
854         // Quake3 colors
855         // LadyHavoc: why on earth is cyan before magenta in Quake3?
856         // LadyHavoc: note: Doom3 uses white for [0] and [7]
857         {0.0, 0.0, 0.0, 1.0}, // black
858         {1.0, 0.0, 0.0, 1.0}, // red
859         {0.0, 1.0, 0.0, 1.0}, // green
860         {1.0, 1.0, 0.0, 1.0}, // yellow
861         {0.0, 0.0, 1.0, 1.0}, // blue
862         {0.0, 1.0, 1.0, 1.0}, // cyan
863         {1.0, 0.0, 1.0, 1.0}, // magenta
864         {1.0, 1.0, 1.0, 1.0}, // white
865         // [515]'s BX_COLOREDTEXT extension
866         {1.0, 1.0, 1.0, 0.5}, // half transparent
867         {0.5, 0.5, 0.5, 1.0}  // half brightness
868         // Black's color table
869         //{1.0, 1.0, 1.0, 1.0},
870         //{1.0, 0.0, 0.0, 1.0},
871         //{0.0, 1.0, 0.0, 1.0},
872         //{0.0, 0.0, 1.0, 1.0},
873         //{1.0, 1.0, 0.0, 1.0},
874         //{0.0, 1.0, 1.0, 1.0},
875         //{1.0, 0.0, 1.0, 1.0},
876         //{0.1, 0.1, 0.1, 1.0}
877 };
878
879 #define STRING_COLORS_COUNT     (sizeof(string_colors) / sizeof(vec4_t))
880
881 static void DrawQ_GetTextColor(float color[4], int colorindex, float r, float g, float b, float a, qbool shadow)
882 {
883         float C = r_textcontrast.value;
884         float B = r_textbrightness.value;
885         if (colorindex & 0x10000) // that bit means RGB color
886         {
887                 color[0] = ((colorindex >> 12) & 0xf) / 15.0;
888                 color[1] = ((colorindex >> 8) & 0xf) / 15.0;
889                 color[2] = ((colorindex >> 4) & 0xf) / 15.0;
890                 color[3] = (colorindex & 0xf) / 15.0;
891         }
892         else
893                 Vector4Copy(string_colors[colorindex], color);
894         Vector4Set(color, color[0] * r * C + B, color[1] * g * C + B, color[2] * b * C + B, color[3] * a);
895         if (shadow)
896         {
897                 float shadowalpha = (color[0]+color[1]+color[2]) * 0.8;
898                 Vector4Set(color, 0, 0, 0, color[3] * bound(0, shadowalpha, 1));
899         }
900 }
901
902 // returns a colorindex (format 0x1RGBA) if str is a valid RGB string
903 // returns 0 otherwise
904 static int RGBstring_to_colorindex(const char *str)
905 {
906         Uchar ch; 
907         int ind = 0x0001 << 4;
908         do {
909                 if (*str <= '9' && *str >= '0')
910                         ind |= (*str - '0');
911                 else
912                 {
913                         ch = tolower(*str);
914                         if (ch >= 'a' && ch <= 'f')
915                                 ind |= (ch - 87);
916                         else
917                                 return 0;
918                 }
919                 ++str;
920                 ind <<= 4;
921         } while(!(ind & 0x10000));
922         return ind | 0xf; // add costant alpha value
923 }
924
925 // NOTE: this function always draws exactly one character if maxwidth <= 0
926 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)
927 {
928         const char *text_start = text;
929         int colorindex;
930         size_t i;
931         float x = 0;
932         Uchar ch, mapch, nextch;
933         Uchar prevch = 0; // used for kerning
934         float kx;
935         int map_index = 0;
936         size_t bytes_left;
937         ft2_font_map_t *fontmap = NULL;
938         ft2_font_map_t *map = NULL;
939         ft2_font_t *ft2 = fnt->ft2;
940         // float ftbase_x;
941         qbool snap = true;
942         qbool least_one = false;
943         float dw; // display w
944         //float dh; // display h
945         const float *width_of;
946
947         if (!h) h = w;
948         if (!h) {
949                 w = h = 1;
950                 snap = false;
951         }
952         // do this in the end
953         w *= fnt->settings.scale;
954         h *= fnt->settings.scale;
955
956         // find the most fitting size:
957         if (ft2 != NULL)
958         {
959                 if (snap)
960                         map_index = Font_IndexForSize(ft2, h, &w, &h);
961                 else
962                         map_index = Font_IndexForSize(ft2, h, NULL, NULL);
963                 fontmap = Font_MapForIndex(ft2, map_index);
964         }
965
966         dw = w * sw;
967         //dh = h * sh;
968
969         if (*maxlen < 1)
970                 *maxlen = 1<<30;
971
972         if (!outcolor || *outcolor == -1)
973                 colorindex = STRING_COLOR_DEFAULT;
974         else
975                 colorindex = *outcolor;
976
977         // maxwidth /= fnt->scale; // w and h are multiplied by it already
978         // ftbase_x = snap_to_pixel_x(0);
979
980         if(maxwidth <= 0)
981         {
982                 least_one = true;
983                 maxwidth = -maxwidth;
984         }
985
986         //if (snap)
987         //      x = snap_to_pixel_x(x, 0.4); // haha, it's 0 anyway
988
989         if (fontmap)
990                 width_of = fnt->width_of_ft2[map_index];
991         else
992                 width_of = fnt->width_of;
993
994         i = 0;
995         while (((bytes_left = *maxlen - (text - text_start)) > 0) && *text)
996         {
997                 size_t i0 = i;
998                 nextch = ch = u8_getnchar(text, &text, bytes_left);
999                 i = text - text_start;
1000                 if (!ch)
1001                         break;
1002                 if (ch == ' ' && !fontmap)
1003                 {
1004                         if(!least_one || i0) // never skip the first character
1005                                 if(x + width_of[(int) ' '] * dw > maxwidth)
1006                                 {
1007                                         i = i0;
1008                                         break; // oops, can't draw this
1009                                 }
1010                         x += width_of[(int) ' '] * dw;
1011                         continue;
1012                 }
1013                 if (ch == STRING_COLOR_TAG && !ignorecolorcodes && i < *maxlen)
1014                 {
1015                         ch = *text; // colors are ascii, so no u8_ needed
1016                         if (ch <= '9' && ch >= '0') // ^[0-9] found
1017                         {
1018                                 colorindex = ch - '0';
1019                                 ++text;
1020                                 ++i;
1021                                 continue;
1022                         }
1023                         else if (ch == STRING_COLOR_RGB_TAG_CHAR && i + 3 < *maxlen ) // ^x found
1024                         {
1025                                 const char *text_p = &text[1];
1026                                 int tempcolorindex = RGBstring_to_colorindex(text_p);
1027                                 if (tempcolorindex)
1028                                 {
1029                                         colorindex = tempcolorindex;
1030                                         i+=4;
1031                                         text += 4;
1032                                         continue;
1033                                 }
1034                         }
1035                         else if (ch == STRING_COLOR_TAG) // ^^ found
1036                         {
1037                                 i++;
1038                                 text++;
1039                         }
1040                         i--;
1041                 }
1042                 ch = nextch;
1043
1044                 if (!fontmap || (ch <= 0xFF && fontmap->glyphs[ch].image) || (ch >= 0xE000 && ch <= 0xE0FF))
1045                 {
1046                         if (ch > 0xE000)
1047                                 ch -= 0xE000;
1048                         if (ch > 0xFF)
1049                                 continue;
1050                         if (fontmap)
1051                                 map = ft2_oldstyle_map;
1052                         prevch = 0;
1053                         if(!least_one || i0) // never skip the first character
1054                                 if(x + width_of[ch] * dw > maxwidth)
1055                                 {
1056                                         i = i0;
1057                                         break; // oops, can't draw this
1058                                 }
1059                         x += width_of[ch] * dw;
1060                 } else {
1061                         if (!map || map == ft2_oldstyle_map || ch != prevch)
1062                         {
1063                                 Font_GetMapForChar(ft2, map_index, ch, &map, &mapch);
1064                                 if (!map)
1065                                         break;
1066                         }
1067                         if (prevch && Font_GetKerningForMap(ft2, map_index, w, h, prevch, ch, &kx, NULL))
1068                                 x += kx * dw;
1069                         x += map->glyphs[mapch].advance_x * dw;
1070                         prevch = ch;
1071                 }
1072         }
1073
1074         *maxlen = i;
1075
1076         if (outcolor)
1077                 *outcolor = colorindex;
1078
1079         return x;
1080 }
1081
1082 float DrawQ_Color[4];
1083 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)
1084 {
1085         int shadow, colorindex = STRING_COLOR_DEFAULT;
1086         size_t i;
1087         float x = startx, y, s, t, u, v, thisw;
1088         Uchar ch, mapch, nextch;
1089         Uchar prevch = 0; // used for kerning
1090         int map_index = 0;
1091         ft2_font_map_t *map = NULL;     // the currently used map
1092         ft2_font_map_t *fontmap = NULL; // the font map for the size
1093         float ftbase_y;
1094         const char *text_start = text;
1095         float kx, ky;
1096         ft2_font_t *ft2 = fnt->ft2;
1097         qbool snap = true;
1098         float pix_x, pix_y;
1099         size_t bytes_left;
1100         float dw, dh;
1101         const float *width_of;
1102         model_t *mod = CL_Mesh_UI();
1103         msurface_t *surf = NULL;
1104         int e0, e1, e2, e3;
1105         int tw, th;
1106         tw = Draw_GetPicWidth(fnt->pic);
1107         th = Draw_GetPicHeight(fnt->pic);
1108
1109         if (!h) h = w;
1110         if (!h) {
1111                 h = w = 1;
1112                 snap = false;
1113         }
1114
1115         starty -= (fnt->settings.scale - 1) * h * 0.5 - fnt->settings.voffset*h; // center & offset
1116         w *= fnt->settings.scale;
1117         h *= fnt->settings.scale;
1118
1119         if (ft2 != NULL)
1120         {
1121                 if (snap)
1122                         map_index = Font_IndexForSize(ft2, h, &w, &h);
1123                 else
1124                         map_index = Font_IndexForSize(ft2, h, NULL, NULL);
1125                 fontmap = Font_MapForIndex(ft2, map_index);
1126         }
1127
1128         dw = w * sw;
1129         dh = h * sh;
1130
1131         // draw the font at its baseline when using freetype
1132         //ftbase_x = 0;
1133         ftbase_y = dh * (4.5/6.0);
1134
1135         if (maxlen < 1)
1136                 maxlen = 1<<30;
1137
1138         if(!r_draw2d.integer && !r_draw2d_force)
1139                 return startx + DrawQ_TextWidth_UntilWidth_TrackColors_Scale(text, &maxlen, w, h, sw, sh, NULL, ignorecolorcodes, fnt, 1000000000);
1140
1141         //ftbase_x = snap_to_pixel_x(ftbase_x);
1142         if(snap)
1143         {
1144                 startx = snap_to_pixel_x(startx, 0.4);
1145                 starty = snap_to_pixel_y(starty, 0.4);
1146                 ftbase_y = snap_to_pixel_y(ftbase_y, 0.3);
1147         }
1148
1149         pix_x = vid.mode.width / vid_conwidth.value;
1150         pix_y = vid.mode.height / vid_conheight.value;
1151
1152         if (fontmap)
1153                 width_of = fnt->width_of_ft2[map_index];
1154         else
1155                 width_of = fnt->width_of;
1156
1157         for (shadow = r_textshadow.value != 0 && basealpha > 0;shadow >= 0;shadow--)
1158         {
1159                 prevch = 0;
1160                 text = text_start;
1161
1162                 if (!outcolor || *outcolor == -1)
1163                         colorindex = STRING_COLOR_DEFAULT;
1164                 else
1165                         colorindex = *outcolor;
1166
1167                 DrawQ_GetTextColor(DrawQ_Color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0);
1168
1169                 x = startx;
1170                 y = starty;
1171                 /*
1172                 if (shadow)
1173                 {
1174                         x += r_textshadow.value * vid.width / vid_conwidth.value;
1175                         y += r_textshadow.value * vid.height / vid_conheight.value;
1176                 }
1177                 */
1178                 while (((bytes_left = maxlen - (text - text_start)) > 0) && *text)
1179                 {
1180                         nextch = ch = u8_getnchar(text, &text, bytes_left);
1181                         i = text - text_start;
1182                         if (!ch)
1183                                 break;
1184                         if (ch == ' ' && !fontmap)
1185                         {
1186                                 x += width_of[(int) ' '] * dw;
1187                                 continue;
1188                         }
1189                         if (ch == STRING_COLOR_TAG && !ignorecolorcodes && i < maxlen)
1190                         {
1191                                 ch = *text; // colors are ascii, so no u8_ needed
1192                                 if (ch <= '9' && ch >= '0') // ^[0-9] found
1193                                 {
1194                                         colorindex = ch - '0';
1195                                         DrawQ_GetTextColor(DrawQ_Color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0);
1196                                         ++text;
1197                                         ++i;
1198                                         continue;
1199                                 }
1200                                 else if (ch == STRING_COLOR_RGB_TAG_CHAR && i+3 < maxlen ) // ^x found
1201                                 {
1202                                         const char *text_p = &text[1];
1203                                         int tempcolorindex = RGBstring_to_colorindex(text_p);
1204                                         if(tempcolorindex)
1205                                         {
1206                                                 colorindex = tempcolorindex;
1207                                                 DrawQ_GetTextColor(DrawQ_Color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0);
1208                                                 i+=4;
1209                                                 text+=4;
1210                                                 continue;
1211                                         }
1212                                 }
1213                                 else if (ch == STRING_COLOR_TAG)
1214                                 {
1215                                         i++;
1216                                         text++;
1217                                 }
1218                                 i--;
1219                         }
1220                         // get the backup
1221                         ch = nextch;
1222                         // using a value of -1 for the oldstyle map because NULL means uninitialized...
1223                         // this way we don't need to rebind fnt->tex for every old-style character
1224                         // E000..E0FF: emulate old-font characters (to still have smileys and such available)
1225                         if (shadow)
1226                         {
1227                                 x += 1.0/pix_x * r_textshadow.value;
1228                                 y += 1.0/pix_y * r_textshadow.value;
1229                         }
1230                         if (!fontmap || (ch <= 0xFF && fontmap->glyphs[ch].image) || (ch >= 0xE000 && ch <= 0xE0FF))
1231                         {
1232                                 if (ch >= 0xE000)
1233                                         ch -= 0xE000;
1234                                 if (ch > 0xFF)
1235                                         goto out;
1236                                 if (fontmap)
1237                                         map = ft2_oldstyle_map;
1238                                 prevch = 0;
1239                                 //num = (unsigned char) text[i];
1240                                 //thisw = fnt->width_of[num];
1241                                 thisw = fnt->width_of[ch];
1242                                 // FIXME make these smaller to just include the occupied part of the character for slightly faster rendering
1243                                 if (r_nearest_conchars.integer)
1244                                 {
1245                                         s = (ch & 15)*0.0625f;
1246                                         t = (ch >> 4)*0.0625f;
1247                                         u = 0.0625f * thisw;
1248                                         v = 0.0625f;
1249                                 }
1250                                 else
1251                                 {
1252                                         s = (ch & 15)*0.0625f + (0.5f / tw);
1253                                         t = (ch >> 4)*0.0625f + (0.5f / th);
1254                                         u = 0.0625f * thisw - (1.0f / tw);
1255                                         v = 0.0625f - (1.0f / th);
1256                                 }
1257                                 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);
1258                                 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]);
1259                                 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]);
1260                                 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]);
1261                                 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]);
1262                                 Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
1263                                 Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
1264                                 x += width_of[ch] * dw;
1265                         } else {
1266                                 if (!map || map == ft2_oldstyle_map || ch != prevch)
1267                                 {
1268                                         Font_GetMapForChar(ft2, map_index, ch, &map, &mapch);
1269                                         if (!map)
1270                                         {
1271                                                 shadow = -1;
1272                                                 break;
1273                                         }
1274                                 }
1275
1276                                 thisw = map->glyphs[mapch].advance_x;
1277
1278                                 //x += ftbase_x;
1279                                 y += ftbase_y;
1280                                 if (prevch && Font_GetKerningForMap(ft2, map_index, w, h, prevch, ch, &kx, &ky))
1281                                 {
1282                                         x += kx * dw;
1283                                         y += ky * dh;
1284                                 }
1285                                 else
1286                                         kx = ky = 0;
1287                                 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);
1288                                 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]);
1289                                 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]);
1290                                 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]);
1291                                 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]);
1292                                 Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
1293                                 Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
1294                                 //x -= ftbase_x;
1295                                 y -= ftbase_y;
1296
1297                                 x += thisw * dw;
1298
1299                                 //prevmap = map;
1300                                 prevch = ch;
1301                         }
1302 out:
1303                         if (shadow)
1304                         {
1305                                 x -= 1.0/pix_x * r_textshadow.value;
1306                                 y -= 1.0/pix_y * r_textshadow.value;
1307                         }
1308                 }
1309         }
1310
1311         if (outcolor)
1312                 *outcolor = colorindex;
1313
1314         // note: this relies on the proper text (not shadow) being drawn last
1315         return x;
1316 }
1317
1318 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)
1319 {
1320         return DrawQ_String_Scale(startx, starty, text, maxlen, w, h, 1, 1, basered, basegreen, baseblue, basealpha, flags, outcolor, ignorecolorcodes, fnt);
1321 }
1322
1323 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)
1324 {
1325         return DrawQ_TextWidth_UntilWidth_TrackColors_Scale(text, maxlen, w, h, 1, 1, outcolor, ignorecolorcodes, fnt, maxwidth);
1326 }
1327
1328 float DrawQ_TextWidth(const char *text, size_t maxlen, float w, float h, qbool ignorecolorcodes, const dp_font_t *fnt)
1329 {
1330         return DrawQ_TextWidth_UntilWidth(text, &maxlen, w, h, ignorecolorcodes, fnt, 1000000000);
1331 }
1332
1333 float DrawQ_TextWidth_UntilWidth(const char *text, size_t *maxlen, float w, float h, qbool ignorecolorcodes, const dp_font_t *fnt, float maxWidth)
1334 {
1335         return DrawQ_TextWidth_UntilWidth_TrackColors(text, maxlen, w, h, NULL, ignorecolorcodes, fnt, maxWidth);
1336 }
1337
1338 #if 0
1339 // not used
1340 // no ^xrgb management
1341 static int DrawQ_BuildColoredText(char *output2c, size_t maxoutchars, const char *text, int maxreadchars, qbool ignorecolorcodes, int *outcolor)
1342 {
1343         int color, numchars = 0;
1344         char *outputend2c = output2c + maxoutchars - 2;
1345         if (!outcolor || *outcolor == -1)
1346                 color = STRING_COLOR_DEFAULT;
1347         else
1348                 color = *outcolor;
1349         if (!maxreadchars)
1350                 maxreadchars = 1<<30;
1351         textend = text + maxreadchars;
1352         while (text != textend && *text)
1353         {
1354                 if (*text == STRING_COLOR_TAG && !ignorecolorcodes && text + 1 != textend)
1355                 {
1356                         if (text[1] == STRING_COLOR_TAG)
1357                                 text++;
1358                         else if (text[1] >= '0' && text[1] <= '9')
1359                         {
1360                                 color = text[1] - '0';
1361                                 text += 2;
1362                                 continue;
1363                         }
1364                 }
1365                 if (output2c >= outputend2c)
1366                         break;
1367                 *output2c++ = *text++;
1368                 *output2c++ = color;
1369                 numchars++;
1370         }
1371         output2c[0] = output2c[1] = 0;
1372         if (outcolor)
1373                 *outcolor = color;
1374         return numchars;
1375 }
1376 #endif
1377
1378 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)
1379 {
1380         model_t *mod = CL_Mesh_UI();
1381         msurface_t *surf;
1382         int e0, e1, e2, e3;
1383         if (!pic)
1384                 pic = Draw_CachePic("white");
1385         // make sure pic is loaded - we don't use the texture here, Mod_Mesh_GetTexture looks up the skinframe by name
1386         Draw_GetPicTexture(pic);
1387         if (width == 0)
1388                 width = pic->width;
1389         if (height == 0)
1390                 height = pic->height;
1391         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);
1392         e0 = Mod_Mesh_IndexForVertex(mod, surf, x        , y         , 0, 0, 0, -1, s1, t1, 0, 0, r1, g1, b1, a1);
1393         e1 = Mod_Mesh_IndexForVertex(mod, surf, x + width, y         , 0, 0, 0, -1, s2, t2, 0, 0, r2, g2, b2, a2);
1394         e2 = Mod_Mesh_IndexForVertex(mod, surf, x + width, y + height, 0, 0, 0, -1, s4, t4, 0, 0, r4, g4, b4, a4);
1395         e3 = Mod_Mesh_IndexForVertex(mod, surf, x        , y + height, 0, 0, 0, -1, s3, t3, 0, 0, r3, g3, b3, a3);
1396         Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
1397         Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
1398 }
1399
1400 void DrawQ_Line (float width, float x1, float y1, float x2, float y2, float r, float g, float b, float alpha, int flags)
1401 {
1402         model_t *mod = CL_Mesh_UI();
1403         msurface_t *surf;
1404         int e0, e1, e2, e3;
1405         float offsetx, offsety;
1406         // width is measured in real pixels
1407         if (fabs(x2 - x1) > fabs(y2 - y1))
1408         {
1409                 offsetx = 0;
1410                 offsety = 0.5f * width * vid_conheight.value / vid.mode.height;
1411         }
1412         else
1413         {
1414                 offsetx = 0.5f * width * vid_conwidth.value / vid.mode.width;
1415                 offsety = 0;
1416         }
1417         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);
1418         e0 = Mod_Mesh_IndexForVertex(mod, surf, x1 - offsetx, y1 - offsety, 10, 0, 0, -1, 0, 0, 0, 0, r, g, b, alpha);
1419         e1 = Mod_Mesh_IndexForVertex(mod, surf, x2 - offsetx, y2 - offsety, 10, 0, 0, -1, 0, 0, 0, 0, r, g, b, alpha);
1420         e2 = Mod_Mesh_IndexForVertex(mod, surf, x2 + offsetx, y2 + offsety, 10, 0, 0, -1, 0, 0, 0, 0, r, g, b, alpha);
1421         e3 = Mod_Mesh_IndexForVertex(mod, surf, x1 + offsetx, y1 + offsety, 10, 0, 0, -1, 0, 0, 0, 0, r, g, b, alpha);
1422         Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
1423         Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
1424 }
1425
1426 void DrawQ_SetClipArea(float x, float y, float width, float height)
1427 {
1428         int ix, iy, iw, ih;
1429         DrawQ_FlushUI();
1430
1431         // We have to convert the con coords into real coords
1432         // OGL uses bottom to top (origin is in bottom left)
1433         ix = (int)(0.5 + x * ((float)r_refdef.view.width / vid_conwidth.integer)) + r_refdef.view.x;
1434         iy = (int)(0.5 + y * ((float)r_refdef.view.height / vid_conheight.integer)) + r_refdef.view.y;
1435         iw = (int)(0.5 + width * ((float)r_refdef.view.width / vid_conwidth.integer));
1436         ih = (int)(0.5 + height * ((float)r_refdef.view.height / vid_conheight.integer));
1437         switch(vid.renderpath)
1438         {
1439         case RENDERPATH_GL32:
1440         case RENDERPATH_GLES2:
1441                 GL_Scissor(ix, vid.mode.height - iy - ih, iw, ih);
1442                 break;
1443         }
1444
1445         GL_ScissorTest(true);
1446 }
1447
1448 void DrawQ_ResetClipArea(void)
1449 {
1450         DrawQ_FlushUI();
1451         GL_ScissorTest(false);
1452 }
1453
1454 void DrawQ_Finish(void)
1455 {
1456         DrawQ_FlushUI();
1457         r_refdef.draw2dstage = 0;
1458 }
1459
1460 void DrawQ_RecalcView(void)
1461 {
1462         DrawQ_FlushUI();
1463         if(r_refdef.draw2dstage)
1464                 r_refdef.draw2dstage = -1; // next draw call will set viewport etc. again
1465 }
1466
1467 void DrawQ_FlushUI(void)
1468 {
1469         model_t *mod = CL_Mesh_UI();
1470         if (mod->num_surfaces == 0)
1471                 return;
1472
1473         if (!r_draw2d.integer && !r_draw2d_force)
1474         {
1475                 Mod_Mesh_Reset(mod);
1476                 return;
1477         }
1478
1479         // this is roughly equivalent to R_Mod_Draw, so the UI can use full material feature set
1480         r_refdef.view.colorscale = 1;
1481         r_textureframe++; // used only by R_GetCurrentTexture
1482         GL_DepthMask(false);
1483
1484         Mod_Mesh_Finalize(mod);
1485         R_DrawModelSurfaces(&cl_meshentities[MESH_UI].render, false, false, false, false, false, true);
1486
1487         Mod_Mesh_Reset(mod);
1488 }