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