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