]> de.git.xonotic.org Git - xonotic/darkplaces.git/blob - gl_draw.c
fix wrong outchar given when merging incmaps;
[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_t *ft2 = fnt->ft2;
931         // float ftbase_x;
932         qbool snap = true;
933         qbool least_one = false;
934         float dw; // display w
935         //float dh; // display h
936         const float *width_of;
937
938         if (!h) h = w;
939         if (!h) {
940                 w = h = 1;
941                 snap = false;
942         }
943         // do this in the end
944         w *= fnt->settings.scale;
945         h *= fnt->settings.scale;
946
947         // find the most fitting size:
948         if (ft2 != NULL)
949         {
950                 if (snap)
951                         map_index = Font_IndexForSize(ft2, h, &w, &h);
952                 else
953                         map_index = Font_IndexForSize(ft2, h, NULL, NULL);
954                 fontmap = Font_MapForIndex(ft2, map_index);
955         }
956
957         dw = w * sw;
958         //dh = h * sh;
959
960         if (*maxlen < 1)
961                 *maxlen = 1<<30;
962
963         if (!outcolor || *outcolor == -1)
964                 colorindex = STRING_COLOR_DEFAULT;
965         else
966                 colorindex = *outcolor;
967
968         // maxwidth /= fnt->scale; // w and h are multiplied by it already
969         // ftbase_x = snap_to_pixel_x(0);
970
971         if(maxwidth <= 0)
972         {
973                 least_one = true;
974                 maxwidth = -maxwidth;
975         }
976
977         //if (snap)
978         //      x = snap_to_pixel_x(x, 0.4); // haha, it's 0 anyway
979
980         if (fontmap)
981                 width_of = fnt->width_of_ft2[map_index];
982         else
983                 width_of = fnt->width_of;
984
985         i = 0;
986         while (((bytes_left = *maxlen - (text - text_start)) > 0) && *text)
987         {
988                 size_t i0 = i;
989                 nextch = ch = u8_getnchar(text, &text, bytes_left);
990                 i = text - text_start;
991                 if (!ch)
992                         break;
993                 if (ch == ' ' && !fontmap)
994                 {
995                         if(!least_one || i0) // never skip the first character
996                                 if(x + width_of[(int) ' '] * dw > maxwidth)
997                                 {
998                                         i = i0;
999                                         break; // oops, can't draw this
1000                                 }
1001                         x += width_of[(int) ' '] * dw;
1002                         continue;
1003                 }
1004                 if (ch == STRING_COLOR_TAG && !ignorecolorcodes && i < *maxlen)
1005                 {
1006                         ch = *text; // colors are ascii, so no u8_ needed
1007                         if (ch <= '9' && ch >= '0') // ^[0-9] found
1008                         {
1009                                 colorindex = ch - '0';
1010                                 ++text;
1011                                 ++i;
1012                                 continue;
1013                         }
1014                         else if (ch == STRING_COLOR_RGB_TAG_CHAR && i + 3 < *maxlen ) // ^x found
1015                         {
1016                                 const char *text_p = &text[1];
1017                                 int tempcolorindex = RGBstring_to_colorindex(text_p);
1018                                 if (tempcolorindex)
1019                                 {
1020                                         colorindex = tempcolorindex;
1021                                         i+=4;
1022                                         text += 4;
1023                                         continue;
1024                                 }
1025                         }
1026                         else if (ch == STRING_COLOR_TAG) // ^^ found
1027                         {
1028                                 i++;
1029                                 text++;
1030                         }
1031                         i--;
1032                 }
1033                 ch = nextch;
1034
1035                 if (!fontmap || (ch <= 0xFF && fontmap->glyphs[ch].image) || (ch >= 0xE000 && ch <= 0xE0FF))
1036                 {
1037                         if (ch > 0xE000)
1038                                 ch -= 0xE000;
1039                         if (ch > 0xFF)
1040                                 continue;
1041                         if (fontmap)
1042                                 map = ft2_oldstyle_map;
1043                         prevch = 0;
1044                         if(!least_one || i0) // never skip the first character
1045                                 if(x + width_of[ch] * dw > maxwidth)
1046                                 {
1047                                         i = i0;
1048                                         break; // oops, can't draw this
1049                                 }
1050                         x += width_of[ch] * dw;
1051                 } else {
1052                         if (!map || map == ft2_oldstyle_map || ch != prevch)
1053                         {
1054                                 Font_GetMapForChar(ft2, map_index, ch, &map, &mapch);
1055                                 if (!map)
1056                                         break;
1057                         }
1058                         if (prevch && Font_GetKerningForMap(ft2, map_index, w, h, prevch, ch, &kx, NULL))
1059                                 x += kx * dw;
1060                         x += map->glyphs[mapch].advance_x * dw;
1061                         prevch = ch;
1062                 }
1063         }
1064
1065         *maxlen = i;
1066
1067         if (outcolor)
1068                 *outcolor = colorindex;
1069
1070         return x;
1071 }
1072
1073 float DrawQ_Color[4];
1074 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)
1075 {
1076         int shadow, colorindex = STRING_COLOR_DEFAULT;
1077         size_t i;
1078         float x = startx, y, s, t, u, v, thisw;
1079         Uchar ch, mapch, nextch;
1080         Uchar prevch = 0; // used for kerning
1081         int map_index = 0;
1082         ft2_font_map_t *map = NULL;     // the currently used map
1083         ft2_font_map_t *fontmap = NULL; // the font map for the size
1084         float ftbase_y;
1085         const char *text_start = text;
1086         float kx, ky;
1087         ft2_font_t *ft2 = fnt->ft2;
1088         qbool snap = true;
1089         float pix_x, pix_y;
1090         size_t bytes_left;
1091         float dw, dh;
1092         const float *width_of;
1093         model_t *mod = CL_Mesh_UI();
1094         msurface_t *surf = NULL;
1095         int e0, e1, e2, e3;
1096         int tw, th;
1097         tw = Draw_GetPicWidth(fnt->pic);
1098         th = Draw_GetPicHeight(fnt->pic);
1099
1100         if (!h) h = w;
1101         if (!h) {
1102                 h = w = 1;
1103                 snap = false;
1104         }
1105
1106         starty -= (fnt->settings.scale - 1) * h * 0.5 - fnt->settings.voffset*h; // center & offset
1107         w *= fnt->settings.scale;
1108         h *= fnt->settings.scale;
1109
1110         if (ft2 != NULL)
1111         {
1112                 if (snap)
1113                         map_index = Font_IndexForSize(ft2, h, &w, &h);
1114                 else
1115                         map_index = Font_IndexForSize(ft2, h, NULL, NULL);
1116                 fontmap = Font_MapForIndex(ft2, map_index);
1117         }
1118
1119         dw = w * sw;
1120         dh = h * sh;
1121
1122         // draw the font at its baseline when using freetype
1123         //ftbase_x = 0;
1124         ftbase_y = dh * (4.5/6.0);
1125
1126         if (maxlen < 1)
1127                 maxlen = 1<<30;
1128
1129         if(!r_draw2d.integer && !r_draw2d_force)
1130                 return startx + DrawQ_TextWidth_UntilWidth_TrackColors_Scale(text, &maxlen, w, h, sw, sh, NULL, ignorecolorcodes, fnt, 1000000000);
1131
1132         //ftbase_x = snap_to_pixel_x(ftbase_x);
1133         if(snap)
1134         {
1135                 startx = snap_to_pixel_x(startx, 0.4);
1136                 starty = snap_to_pixel_y(starty, 0.4);
1137                 ftbase_y = snap_to_pixel_y(ftbase_y, 0.3);
1138         }
1139
1140         pix_x = vid.width / vid_conwidth.value;
1141         pix_y = vid.height / vid_conheight.value;
1142
1143         if (fontmap)
1144                 width_of = fnt->width_of_ft2[map_index];
1145         else
1146                 width_of = fnt->width_of;
1147
1148         for (shadow = r_textshadow.value != 0 && basealpha > 0;shadow >= 0;shadow--)
1149         {
1150                 prevch = 0;
1151                 text = text_start;
1152
1153                 if (!outcolor || *outcolor == -1)
1154                         colorindex = STRING_COLOR_DEFAULT;
1155                 else
1156                         colorindex = *outcolor;
1157
1158                 DrawQ_GetTextColor(DrawQ_Color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0);
1159
1160                 x = startx;
1161                 y = starty;
1162                 /*
1163                 if (shadow)
1164                 {
1165                         x += r_textshadow.value * vid.width / vid_conwidth.value;
1166                         y += r_textshadow.value * vid.height / vid_conheight.value;
1167                 }
1168                 */
1169                 while (((bytes_left = maxlen - (text - text_start)) > 0) && *text)
1170                 {
1171                         nextch = ch = u8_getnchar(text, &text, bytes_left);
1172                         i = text - text_start;
1173                         if (!ch)
1174                                 break;
1175                         if (ch == ' ' && !fontmap)
1176                         {
1177                                 x += width_of[(int) ' '] * dw;
1178                                 continue;
1179                         }
1180                         if (ch == STRING_COLOR_TAG && !ignorecolorcodes && i < maxlen)
1181                         {
1182                                 ch = *text; // colors are ascii, so no u8_ needed
1183                                 if (ch <= '9' && ch >= '0') // ^[0-9] found
1184                                 {
1185                                         colorindex = ch - '0';
1186                                         DrawQ_GetTextColor(DrawQ_Color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0);
1187                                         ++text;
1188                                         ++i;
1189                                         continue;
1190                                 }
1191                                 else if (ch == STRING_COLOR_RGB_TAG_CHAR && i+3 < maxlen ) // ^x found
1192                                 {
1193                                         const char *text_p = &text[1];
1194                                         int tempcolorindex = RGBstring_to_colorindex(text_p);
1195                                         if(tempcolorindex)
1196                                         {
1197                                                 colorindex = tempcolorindex;
1198                                                 DrawQ_GetTextColor(DrawQ_Color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0);
1199                                                 i+=4;
1200                                                 text+=4;
1201                                                 continue;
1202                                         }
1203                                 }
1204                                 else if (ch == STRING_COLOR_TAG)
1205                                 {
1206                                         i++;
1207                                         text++;
1208                                 }
1209                                 i--;
1210                         }
1211                         // get the backup
1212                         ch = nextch;
1213                         // using a value of -1 for the oldstyle map because NULL means uninitialized...
1214                         // this way we don't need to rebind fnt->tex for every old-style character
1215                         // E000..E0FF: emulate old-font characters (to still have smileys and such available)
1216                         if (shadow)
1217                         {
1218                                 x += 1.0/pix_x * r_textshadow.value;
1219                                 y += 1.0/pix_y * r_textshadow.value;
1220                         }
1221                         if (!fontmap || (ch <= 0xFF && fontmap->glyphs[ch].image) || (ch >= 0xE000 && ch <= 0xE0FF))
1222                         {
1223                                 if (ch >= 0xE000)
1224                                         ch -= 0xE000;
1225                                 if (ch > 0xFF)
1226                                         goto out;
1227                                 if (fontmap)
1228                                         map = ft2_oldstyle_map;
1229                                 prevch = 0;
1230                                 //num = (unsigned char) text[i];
1231                                 //thisw = fnt->width_of[num];
1232                                 thisw = fnt->width_of[ch];
1233                                 // FIXME make these smaller to just include the occupied part of the character for slightly faster rendering
1234                                 if (r_nearest_conchars.integer)
1235                                 {
1236                                         s = (ch & 15)*0.0625f;
1237                                         t = (ch >> 4)*0.0625f;
1238                                         u = 0.0625f * thisw;
1239                                         v = 0.0625f;
1240                                 }
1241                                 else
1242                                 {
1243                                         s = (ch & 15)*0.0625f + (0.5f / tw);
1244                                         t = (ch >> 4)*0.0625f + (0.5f / th);
1245                                         u = 0.0625f * thisw - (1.0f / tw);
1246                                         v = 0.0625f - (1.0f / th);
1247                                 }
1248                                 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);
1249                                 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]);
1250                                 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]);
1251                                 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]);
1252                                 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]);
1253                                 Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
1254                                 Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
1255                                 x += width_of[ch] * dw;
1256                         } else {
1257                                 if (!map || map == ft2_oldstyle_map || ch != prevch)
1258                                 {
1259                                         Font_GetMapForChar(ft2, map_index, ch, &map, &mapch);
1260                                         if (!map)
1261                                         {
1262                                                 shadow = -1;
1263                                                 break;
1264                                         }
1265                                 }
1266
1267                                 thisw = map->glyphs[mapch].advance_x;
1268
1269                                 //x += ftbase_x;
1270                                 y += ftbase_y;
1271                                 if (prevch && Font_GetKerningForMap(ft2, map_index, w, h, prevch, ch, &kx, &ky))
1272                                 {
1273                                         x += kx * dw;
1274                                         y += ky * dh;
1275                                 }
1276                                 else
1277                                         kx = ky = 0;
1278                                 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);
1279                                 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]);
1280                                 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]);
1281                                 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]);
1282                                 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]);
1283                                 Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
1284                                 Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
1285                                 //x -= ftbase_x;
1286                                 y -= ftbase_y;
1287
1288                                 x += thisw * dw;
1289
1290                                 //prevmap = map;
1291                                 prevch = ch;
1292                         }
1293 out:
1294                         if (shadow)
1295                         {
1296                                 x -= 1.0/pix_x * r_textshadow.value;
1297                                 y -= 1.0/pix_y * r_textshadow.value;
1298                         }
1299                 }
1300         }
1301
1302         if (outcolor)
1303                 *outcolor = colorindex;
1304
1305         // note: this relies on the proper text (not shadow) being drawn last
1306         return x;
1307 }
1308
1309 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)
1310 {
1311         return DrawQ_String_Scale(startx, starty, text, maxlen, w, h, 1, 1, basered, basegreen, baseblue, basealpha, flags, outcolor, ignorecolorcodes, fnt);
1312 }
1313
1314 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)
1315 {
1316         return DrawQ_TextWidth_UntilWidth_TrackColors_Scale(text, maxlen, w, h, 1, 1, outcolor, ignorecolorcodes, fnt, maxwidth);
1317 }
1318
1319 float DrawQ_TextWidth(const char *text, size_t maxlen, float w, float h, qbool ignorecolorcodes, const dp_font_t *fnt)
1320 {
1321         return DrawQ_TextWidth_UntilWidth(text, &maxlen, w, h, ignorecolorcodes, fnt, 1000000000);
1322 }
1323
1324 float DrawQ_TextWidth_UntilWidth(const char *text, size_t *maxlen, float w, float h, qbool ignorecolorcodes, const dp_font_t *fnt, float maxWidth)
1325 {
1326         return DrawQ_TextWidth_UntilWidth_TrackColors(text, maxlen, w, h, NULL, ignorecolorcodes, fnt, maxWidth);
1327 }
1328
1329 #if 0
1330 // not used
1331 // no ^xrgb management
1332 static int DrawQ_BuildColoredText(char *output2c, size_t maxoutchars, const char *text, int maxreadchars, qbool ignorecolorcodes, int *outcolor)
1333 {
1334         int color, numchars = 0;
1335         char *outputend2c = output2c + maxoutchars - 2;
1336         if (!outcolor || *outcolor == -1)
1337                 color = STRING_COLOR_DEFAULT;
1338         else
1339                 color = *outcolor;
1340         if (!maxreadchars)
1341                 maxreadchars = 1<<30;
1342         textend = text + maxreadchars;
1343         while (text != textend && *text)
1344         {
1345                 if (*text == STRING_COLOR_TAG && !ignorecolorcodes && text + 1 != textend)
1346                 {
1347                         if (text[1] == STRING_COLOR_TAG)
1348                                 text++;
1349                         else if (text[1] >= '0' && text[1] <= '9')
1350                         {
1351                                 color = text[1] - '0';
1352                                 text += 2;
1353                                 continue;
1354                         }
1355                 }
1356                 if (output2c >= outputend2c)
1357                         break;
1358                 *output2c++ = *text++;
1359                 *output2c++ = color;
1360                 numchars++;
1361         }
1362         output2c[0] = output2c[1] = 0;
1363         if (outcolor)
1364                 *outcolor = color;
1365         return numchars;
1366 }
1367 #endif
1368
1369 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)
1370 {
1371         model_t *mod = CL_Mesh_UI();
1372         msurface_t *surf;
1373         int e0, e1, e2, e3;
1374         if (!pic)
1375                 pic = Draw_CachePic("white");
1376         // make sure pic is loaded - we don't use the texture here, Mod_Mesh_GetTexture looks up the skinframe by name
1377         Draw_GetPicTexture(pic);
1378         if (width == 0)
1379                 width = pic->width;
1380         if (height == 0)
1381                 height = pic->height;
1382         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);
1383         e0 = Mod_Mesh_IndexForVertex(mod, surf, x        , y         , 0, 0, 0, -1, s1, t1, 0, 0, r1, g1, b1, a1);
1384         e1 = Mod_Mesh_IndexForVertex(mod, surf, x + width, y         , 0, 0, 0, -1, s2, t2, 0, 0, r2, g2, b2, a2);
1385         e2 = Mod_Mesh_IndexForVertex(mod, surf, x + width, y + height, 0, 0, 0, -1, s4, t4, 0, 0, r4, g4, b4, a4);
1386         e3 = Mod_Mesh_IndexForVertex(mod, surf, x        , y + height, 0, 0, 0, -1, s3, t3, 0, 0, r3, g3, b3, a3);
1387         Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
1388         Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
1389 }
1390
1391 void DrawQ_Line (float width, float x1, float y1, float x2, float y2, float r, float g, float b, float alpha, int flags)
1392 {
1393         model_t *mod = CL_Mesh_UI();
1394         msurface_t *surf;
1395         int e0, e1, e2, e3;
1396         float offsetx, offsety;
1397         // width is measured in real pixels
1398         if (fabs(x2 - x1) > fabs(y2 - y1))
1399         {
1400                 offsetx = 0;
1401                 offsety = 0.5f * width * vid_conheight.value / vid.height;
1402         }
1403         else
1404         {
1405                 offsetx = 0.5f * width * vid_conwidth.value / vid.width;
1406                 offsety = 0;
1407         }
1408         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);
1409         e0 = Mod_Mesh_IndexForVertex(mod, surf, x1 - offsetx, y1 - offsety, 10, 0, 0, -1, 0, 0, 0, 0, r, g, b, alpha);
1410         e1 = Mod_Mesh_IndexForVertex(mod, surf, x2 - offsetx, y2 - offsety, 10, 0, 0, -1, 0, 0, 0, 0, r, g, b, alpha);
1411         e2 = Mod_Mesh_IndexForVertex(mod, surf, x2 + offsetx, y2 + offsety, 10, 0, 0, -1, 0, 0, 0, 0, r, g, b, alpha);
1412         e3 = Mod_Mesh_IndexForVertex(mod, surf, x1 + offsetx, y1 + offsety, 10, 0, 0, -1, 0, 0, 0, 0, r, g, b, alpha);
1413         Mod_Mesh_AddTriangle(mod, surf, e0, e1, e2);
1414         Mod_Mesh_AddTriangle(mod, surf, e0, e2, e3);
1415 }
1416
1417 void DrawQ_SetClipArea(float x, float y, float width, float height)
1418 {
1419         int ix, iy, iw, ih;
1420         DrawQ_FlushUI();
1421
1422         // We have to convert the con coords into real coords
1423         // OGL uses bottom to top (origin is in bottom left)
1424         ix = (int)(0.5 + x * ((float)r_refdef.view.width / vid_conwidth.integer)) + r_refdef.view.x;
1425         iy = (int)(0.5 + y * ((float)r_refdef.view.height / vid_conheight.integer)) + r_refdef.view.y;
1426         iw = (int)(0.5 + width * ((float)r_refdef.view.width / vid_conwidth.integer));
1427         ih = (int)(0.5 + height * ((float)r_refdef.view.height / vid_conheight.integer));
1428         switch(vid.renderpath)
1429         {
1430         case RENDERPATH_GL32:
1431         case RENDERPATH_GLES2:
1432                 GL_Scissor(ix, vid.height - iy - ih, iw, ih);
1433                 break;
1434         }
1435
1436         GL_ScissorTest(true);
1437 }
1438
1439 void DrawQ_ResetClipArea(void)
1440 {
1441         DrawQ_FlushUI();
1442         GL_ScissorTest(false);
1443 }
1444
1445 void DrawQ_Finish(void)
1446 {
1447         DrawQ_FlushUI();
1448         r_refdef.draw2dstage = 0;
1449 }
1450
1451 void DrawQ_RecalcView(void)
1452 {
1453         DrawQ_FlushUI();
1454         if(r_refdef.draw2dstage)
1455                 r_refdef.draw2dstage = -1; // next draw call will set viewport etc. again
1456 }
1457
1458 void DrawQ_FlushUI(void)
1459 {
1460         model_t *mod = CL_Mesh_UI();
1461         if (mod->num_surfaces == 0)
1462                 return;
1463
1464         if (!r_draw2d.integer && !r_draw2d_force)
1465         {
1466                 Mod_Mesh_Reset(mod);
1467                 return;
1468         }
1469
1470         // this is roughly equivalent to R_Mod_Draw, so the UI can use full material feature set
1471         r_refdef.view.colorscale = 1;
1472         r_textureframe++; // used only by R_GetCurrentTexture
1473         GL_DepthMask(false);
1474
1475         Mod_Mesh_Finalize(mod);
1476         R_DrawModelSurfaces(&cl_meshentities[MESH_UI].render, false, false, false, false, false, true);
1477
1478         Mod_Mesh_Reset(mod);
1479 }