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