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