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