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