]> icculus.org git repositories - divverent/darkplaces.git/blob - ft2.c
stuff...
[divverent/darkplaces.git] / ft2.c
1 /* FreeType 2 and UTF-8 encoding support for
2  * DarkPlaces
3  */
4 #include "quakedef.h"
5
6 #include "ft2.h"
7
8 #include "ft2_defs.h"
9
10 /*
11 ================================================================================
12 Function definitions. Taken from the freetype2 headers.
13 ================================================================================
14 */
15
16
17 FT_EXPORT( FT_Error )
18 (*qFT_Init_FreeType)( FT_Library  *alibrary );
19 FT_EXPORT( FT_Error )
20 (*qFT_Done_FreeType)( FT_Library  library );
21 FT_EXPORT( FT_Error )
22 (*qFT_New_Face)( FT_Library   library,
23                  const char*  filepathname,
24                  FT_Long      face_index,
25                  FT_Face     *aface );
26 FT_EXPORT( FT_Error )
27 (*qFT_New_Memory_Face)( FT_Library      library,
28                         const FT_Byte*  file_base,
29                         FT_Long         file_size,
30                         FT_Long         face_index,
31                         FT_Face        *aface );
32 FT_EXPORT( FT_Error )
33 (*qFT_Done_Face)( FT_Face  face );
34 FT_EXPORT( FT_Error )
35 (*qFT_Select_Size)( FT_Face  face,
36                     FT_Int   strike_index );
37 FT_EXPORT( FT_Error )
38 (*qFT_Request_Size)( FT_Face          face,
39                      FT_Size_Request  req );
40 FT_EXPORT( FT_Error )
41 (*qFT_Set_Char_Size)( FT_Face     face,
42                       FT_F26Dot6  char_width,
43                       FT_F26Dot6  char_height,
44                       FT_UInt     horz_resolution,
45                       FT_UInt     vert_resolution );
46 FT_EXPORT( FT_Error )
47 (*qFT_Set_Pixel_Sizes)( FT_Face  face,
48                         FT_UInt  pixel_width,
49                         FT_UInt  pixel_height );
50 FT_EXPORT( FT_Error )
51 (*qFT_Load_Glyph)( FT_Face   face,
52                    FT_UInt   glyph_index,
53                    FT_Int32  load_flags );
54 FT_EXPORT( FT_Error )
55 (*qFT_Load_Char)( FT_Face   face,
56                   FT_ULong  char_code,
57                   FT_Int32  load_flags );
58 FT_EXPORT( FT_UInt )
59 (*qFT_Get_Char_Index)( FT_Face   face,
60                        FT_ULong  charcode );
61 FT_EXPORT( FT_Error )
62 (*qFT_Render_Glyph)( FT_GlyphSlot    slot,
63                      FT_Render_Mode  render_mode );
64 FT_EXPORT( FT_Error )
65 (*qFT_Get_Kerning)( FT_Face     face,
66                     FT_UInt     left_glyph,
67                     FT_UInt     right_glyph,
68                     FT_UInt     kern_mode,
69                     FT_Vector  *akerning );
70
71 /*
72 ================================================================================
73 Support for dynamically loading the FreeType2 library
74 ================================================================================
75 */
76
77 static dllfunction_t ft2funcs[] =
78 {
79         {"FT_Init_FreeType",            (void **) &qFT_Init_FreeType},
80         {"FT_Done_FreeType",            (void **) &qFT_Done_FreeType},
81         {"FT_New_Face",                 (void **) &qFT_New_Face},
82         {"FT_New_Memory_Face",          (void **) &qFT_New_Memory_Face},
83         {"FT_Done_Face",                (void **) &qFT_Done_Face},
84         {"FT_Select_Size",              (void **) &qFT_Select_Size},
85         {"FT_Request_Size",             (void **) &qFT_Request_Size},
86         {"FT_Set_Char_Size",            (void **) &qFT_Set_Char_Size},
87         {"FT_Set_Pixel_Sizes",          (void **) &qFT_Set_Pixel_Sizes},
88         {"FT_Load_Glyph",               (void **) &qFT_Load_Glyph},
89         {"FT_Load_Char",                (void **) &qFT_Load_Char},
90         {"FT_Get_Char_Index",           (void **) &qFT_Get_Char_Index},
91         {"FT_Render_Glyph",             (void **) &qFT_Render_Glyph},
92         {"FT_Get_Kerning",              (void **) &qFT_Get_Kerning},
93         {NULL, NULL}
94 };
95
96 /// Handle for FreeType2 DLL
97 static dllhandle_t ft2_dll = NULL;
98
99 /// Memory pool for fonts
100 static mempool_t *font_mempool= NULL;
101 static rtexturepool_t *font_texturepool = NULL;
102
103 /// FreeType library handle
104 static FT_Library font_ft2lib = NULL;
105
106 /*
107 ====================
108 Font_CloseLibrary
109
110 Unload the FreeType2 DLL
111 ====================
112 */
113 void Font_CloseLibrary (void)
114 {
115         if (font_mempool)
116                 Mem_FreePool(&font_mempool);
117         if (font_texturepool)
118                 R_FreeTexturePool(&font_texturepool);
119         if (font_ft2lib && qFT_Done_FreeType)
120         {
121                 qFT_Done_FreeType(font_ft2lib);
122                 font_ft2lib = NULL;
123         }
124         Sys_UnloadLibrary (&ft2_dll);
125 }
126
127 /*
128 ====================
129 Font_OpenLibrary
130
131 Try to load the FreeType2 DLL
132 ====================
133 */
134 qboolean Font_OpenLibrary (void)
135 {
136         const char* dllnames [] =
137         {
138 #if defined(WIN64)
139                 #error path for freetype 2 dll
140 #elif defined(WIN32)
141                 #error path for freetype 2 dll
142 #elif defined(MACOSX)
143                 "libfreetype.dylib",
144 #else
145                 "libfreetype.so.6",
146                 "libfreetype.so",
147 #endif
148                 NULL
149         };
150
151         // Already loaded?
152         if (ft2_dll)
153                 return true;
154
155         // Load the DLL
156         if (!Sys_LoadLibrary (dllnames, &ft2_dll, ft2funcs))
157                 return false;
158         return true;
159 }
160
161 /*
162 ====================
163 Font_Init
164
165 Initialize the freetype2 font subsystem
166 ====================
167 */
168
169 static font_t test_font;
170
171 static void font_start(void)
172 {
173         if (!Font_OpenLibrary())
174                 return;
175
176         if (qFT_Init_FreeType(&font_ft2lib))
177         {
178                 Con_Print("ERROR: Failed to initialize the FreeType2 library!\n");
179                 Font_CloseLibrary();
180                 return;
181         }
182
183         font_mempool = Mem_AllocPool("FONT", 0, NULL);
184         if (!font_mempool)
185         {
186                 Con_Print("ERROR: Failed to allocate FONT memory pool!\n");
187                 Font_CloseLibrary();
188                 return;
189         }
190
191         font_texturepool = R_AllocTexturePool();
192         if (!font_texturepool)
193         {
194                 Con_Print("ERROR: Failed to allocate FONT texture pool!\n");
195                 Font_CloseLibrary();
196                 return;
197         }
198
199         if (!Font_LoadFont("gfx/test", 16, &test_font))
200         {
201                 Con_Print("ERROR: Failed to load test font!\n");
202                 Font_CloseLibrary();
203                 return;
204         }
205 }
206
207 static void font_shutdown(void)
208 {
209         Font_CloseLibrary();
210 }
211
212 static void font_newmap(void)
213 {
214 }
215
216 void Font_Init(void)
217 {
218         R_RegisterModule("Font_FreeType2", font_start, font_shutdown, font_newmap);
219 }
220
221 /*
222 ================================================================================
223 UTF-8 encoding and decoding functions follow.
224 ================================================================================
225 */
226
227 /** Get the number of characters in in an UTF-8 string.
228  * @param _s    An utf-8 encoded null-terminated string.
229  * @return      The number of unicode characters in the string.
230  */
231 size_t u8_strlen(const char *_s)
232 {
233         size_t len = 0;
234         unsigned char *s = (unsigned char*)_s;
235         while (*s)
236         {
237                 // ascii char
238                 if (*s <= 0x7F)
239                 {
240                         ++len;
241                         ++s;
242                         continue;
243                 }
244
245                 // start of a wide character
246                 if (*s & 0xC0)
247                 {
248                         ++len;
249                         for (++s; *s >= 0x80 && *s <= 0xC0; ++s);
250                         continue;
251                 }
252
253                 // part of a wide character, we ignore that one
254                 if (*s <= 0xBF) // 10111111
255                 {
256                         ++s;
257                         continue;
258                 }
259         }
260         return len;
261 }
262
263 /** Fetch a character from an utf-8 encoded string.
264  * @param _s      The start of an utf-8 encoded multi-byte character.
265  * @param _end    Will point to after the first multi-byte character.
266  * @return        The 32-bit integer representation of the first multi-byte character.
267  */
268 Uchar u8_getchar(const char *_s, const char **_end)
269 {
270         const unsigned char *s = (unsigned char*)_s;
271         Uchar u;
272         unsigned char mask;
273         unsigned char v;
274
275         if (*s < 0x80)
276         {
277                 if (_end)
278                         *_end = _s + 1;
279                 return (U_int32)*s;
280         }
281
282         if (*s < 0xC0)
283         {
284                 // starting within a wide character - skip it and retrieve the one after it
285                 for (++s; *s >= 0x80 && *s < 0xC0; ++s);
286                 // or we could return '?' here?
287         }
288
289         u = 0;
290         mask = 0x7F;
291         v = *s & mask;
292         for (mask >>= 1; v > (*s & mask); mask >>= 1)
293                 v = (*s & mask);
294         u = (Uchar)(*s & mask);
295         for (++s; *s >= 0x80 && *s < 0xC0; ++s)
296                 u = (u << 6) | (*s & 0x3F);
297
298         if (_end)
299                 *_end = (const char*)s;
300
301         return u;
302 }
303
304 /** Encode a wide-character into utf-8.
305  * @param w        The wide character to encode.
306  * @param to       The target buffer the utf-8 encoded string is stored to.
307  * @param maxlen   The maximum number of bytes that fit into the target buffer.
308  * @return         Number of bytes written to the buffer, or less or equal to 0 if the buffer is too small.
309  */
310 int u8_fromchar(Uchar w, char *to, size_t maxlen)
311 {
312         size_t i, j;
313         char bt;
314         char tmp[16];
315
316         if (maxlen < 1)
317                 return -2;
318
319         if (w < 0x80)
320         {
321                 to[0] = (char)w;
322                 if (maxlen < 2)
323                         return -1;
324                 to[1] = 0;
325                 return 1;
326         }
327
328         // check how much space we need and store data into a
329         // temp buffer - this is faster than recalculating again
330         i = 0;
331         bt = 0;
332         while (w)
333         {
334                 tmp[i++] = 0x80 | (w & 0x3F);
335                 bt = (bt >> 1) | 0x80;
336                 w >>= 6;
337                 // see if we still fit into the target buffer
338                 if (i+1 >= maxlen) // +1 for the \0
339                         return -i;
340
341                 // there are no characters which take up that much space yet
342                 // and there won't be for the next many many years, still... let's be safe
343                 if (i >= sizeof(tmp))
344                         return -1;
345         }
346         tmp[i-1] |= bt;
347         for (j = 0; j < i; ++j)
348         {
349                 to[i-j-1] = tmp[j];
350         }
351
352         to[i] = 0;
353         return i;
354 }
355
356 /** Convert a utf-8 multibyte string to a wide character string.
357  * @param wcs       The target wide-character buffer.
358  * @param mb        The utf-8 encoded multibyte string to convert.
359  * @param maxlen    The maximum number of wide-characters that fit into the target buffer.
360  * @return          The number of characters written to the target buffer.
361  */
362 size_t u8_mbstowcs(Uchar *wcs, const char *mb, size_t maxlen)
363 {
364         size_t i;
365         for (i = 0; *mb && i < maxlen; ++i)
366                 *wcs++ = u8_getchar(mb, &mb);
367         if (i < maxlen)
368                 *wcs = 0;
369         return i;
370 }
371
372 /** Convert a wide-character string to a utf-8 multibyte string.
373  * @param mb      The target buffer the utf-8 string is written to.
374  * @param wcs     The wide-character string to convert.
375  * @param maxlen  The number bytes that fit into the multibyte target buffer.
376  * @return        The number of bytes written, not including the terminating \0
377  */
378 size_t u8_wcstombs(char *mb, const Uchar *wcs, size_t maxlen)
379 {
380         size_t i;
381         const char *start = mb;
382         for (i = 0; *wcs && i < maxlen; ++i)
383         {
384                 int len;
385                 if ( (len = u8_fromchar(*wcs++, mb, maxlen - i)) < 0)
386                         return (mb - start);
387                 mb += len;
388         }
389         if (i < maxlen)
390                 *mb = 0;
391         return (mb - start);
392 }
393
394 /*
395 ================================================================================
396 Implementation of a more or less lazy font loading and rendering code.
397 ================================================================================
398 */
399
400 #define FONT_CHARS_PER_LINE 16
401 #define FONT_CHAR_LINES 16
402 #define FONT_CHARS_PER_MAP (FONT_CHARS_PER_LINE * FONT_CHAR_LINES)
403
404 typedef struct glyph_slot_s
405 {
406         // we keep the quad coords here only currently
407         // if you need other info, make Font_LoadMapForIndex fill it into this slot
408         float xmin; // texture coordinate in [0,1]
409         float xmax;
410         float ymin;
411         float ymax;
412         float advance_x;
413         float advance_y;
414 } glyph_slot_t;
415
416 struct font_map_s
417 {
418         Uchar start;
419         struct font_map_s *next;
420
421         rtexture_t *texture;
422         glyph_slot_t glyphs[FONT_CHARS_PER_MAP];
423 };
424
425 static qboolean Font_LoadMapForIndex(font_t *font, Uchar _ch, font_map_t **outmap);
426 qboolean Font_LoadFont(const char *name, int size, font_t *font)
427 {
428         size_t namelen;
429         char filename[PATH_MAX];
430         int status;
431
432         memset(font, 0, sizeof(*font));
433
434         if (!Font_OpenLibrary())
435         {
436                 Con_Printf("WARNING: can't open load font %s\n"
437                            "You need the FreeType2 DLL to load font files\n",
438                            name);
439                 return false;
440         }
441
442         namelen = strlen(name);
443
444         memcpy(filename, name, namelen);
445         memcpy(filename + namelen, ".ttf", 5);
446
447         font->data = FS_LoadFile(filename, font_mempool, false, &font->datasize);
448         if (!font->data)
449         {
450                 // FS_LoadFile being not-quiet should print an error :)
451                 return false;
452         }
453
454
455         status = qFT_New_Memory_Face(font_ft2lib, (FT_Bytes)font->data, font->datasize, 0, (FT_Face*)&font->face);
456         if (status)
457         {
458                 Con_Printf("ERROR: can't create face for %s\n"
459                            "Error %i\n", // TODO: error strings
460                            name, status);
461                 Mem_Free(font->data);
462                 return false;
463         }
464
465         memcpy(font->name, name, namelen+1);
466         font->size = size;
467         font->glyphSize = font->size * 2;
468         font->has_kerning = !!(((FT_Face)(font->face))->face_flags & FT_FACE_FLAG_KERNING);
469
470         status = qFT_Set_Pixel_Sizes((FT_Face)font->face, size, size);
471         if (status)
472         {
473                 Con_Printf("ERROR: can't size pixel sizes for face of font %s\n"
474                            "Error %i\n", // TODO: error strings
475                            name, status);
476                 Mem_Free(font->data);
477                 return false;
478         }
479
480         if (!Font_LoadMapForIndex(font, 0, NULL))
481         {
482                 Con_Printf("ERROR: can't load the first character map for %s\n"
483                            "This is fatal\n",
484                            name);
485                 Mem_Free(font->data);
486                 return false;
487         }
488
489         return true;
490 }
491
492 void Font_UnloadFont(font_t *font)
493 {
494         if (font->data)
495                 Mem_Free(font->data);
496         if (ft2_dll)
497         {
498                 if (font->face)
499                 {
500                         qFT_Done_Face((FT_Face)font->face);
501                         font->face = NULL;
502                 }
503         }
504 }
505
506 static qboolean Font_LoadMapForIndex(font_t *font, Uchar _ch, font_map_t **outmap)
507 {
508         char map_identifier[PATH_MAX];
509         unsigned long mapidx = _ch / FONT_CHARS_PER_MAP;
510         unsigned char *data;
511         FT_ULong ch, mapch;
512         int status;
513
514         FT_Face face = font->face;
515
516         int pitch = font->glyphSize * FONT_CHARS_PER_LINE;
517         int gR, gC; // glyph position: row and column
518
519         font_map_t *map;
520
521         if (outmap)
522                 *outmap = NULL;
523
524         map = Mem_Alloc(font_mempool, sizeof(font_map_t));
525         if (!map)
526         {
527                 Con_Printf("ERROR: Out of memory when loading fontmap for %s\n", font->name);
528                 return false;
529         }
530
531         data = Mem_Alloc(font_mempool, FONT_CHARS_PER_MAP * (font->glyphSize * font->glyphSize));
532         if (!data)
533         {
534                 Mem_Free(map);
535                 Con_Printf("ERROR: Failed to allocate memory for glyph data for font %s\n", font->name);
536                 return false;
537         }
538
539         memset(data, 0, sizeof(data));
540
541         map->start = mapidx * FONT_CHARS_PER_MAP;
542         if (!font->font_map)
543                 font->font_map = map;
544         else
545         {
546                 // insert the map at the right place
547                 font_map_t *next = font->font_map;
548                 while(next->next && next->next->start < map->start)
549                         next = next->next;
550                 map->next = next->next;
551                 next->next = map;
552         }
553
554         gR = gC = 0;
555         for (ch = map->start;
556              ch < (FT_ULong)map->start + FONT_CHARS_PER_MAP;
557              ++ch)
558         {
559                 FT_ULong glyphIndex;
560                 int w, h, x, y;
561                 FT_GlyphSlot glyph;
562                 FT_Bitmap *bmp;
563                 unsigned char *imagedata, *dst, *src;
564                 glyph_slot_t *mapglyph;
565
566                 ++gC;
567                 if (gC >= FONT_CHARS_PER_LINE)
568                 {
569                         gC -= FONT_CHARS_PER_LINE;
570                         ++gR;
571                 }
572
573                 imagedata = data + gR * pitch + gC * font->glyphSize;
574                 glyphIndex = qFT_Get_Char_Index(face, ch);
575
576                 status = qFT_Load_Glyph(face, glyphIndex, FT_LOAD_DEFAULT);
577                 if (status)
578                 {
579                         Con_Printf("failed to load glyph %lu for %s\n", glyphIndex, font->name);
580                         continue;
581                 }
582
583                 status = qFT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);
584                 if (status)
585                 {
586                         Con_Printf("failed to render glyph %lu for %s\n", glyphIndex, font->name);
587                         continue;
588                 }
589
590                 glyph = face->glyph;
591                 bmp = &glyph->bitmap;
592
593                 w = bmp->width;
594                 h = bmp->rows;
595
596                 if (w > font->glyphSize || h > font->glyphSize)
597                         Con_Printf("WARNING: Glyph %lu is too big in font %s\n", ch, font->name);
598
599                 for (y = 0; y < h; ++y)
600                 {
601                         dst = imagedata + y * pitch;
602                         src = bmp->buffer + y * bmp->pitch;
603
604                         switch (bmp->pixel_mode)
605                         {
606                         case FT_PIXEL_MODE_MONO:
607                                 for (x = 0; x < bmp->width; x += 8)
608                                 {
609                                         unsigned char ch = *src++;
610                                         *dst++ = 255 * ((ch & 0x80) >> 7);
611                                         *dst++ = 255 * ((ch & 0x40) >> 6);
612                                         *dst++ = 255 * ((ch & 0x20) >> 5);
613                                         *dst++ = 255 * ((ch & 0x10) >> 4);
614                                         *dst++ = 255 * ((ch & 0x08) >> 3);
615                                         *dst++ = 255 * ((ch & 0x04) >> 2);
616                                         *dst++ = 255 * ((ch & 0x02) >> 1);
617                                         *dst++ = 255 * ((ch & 0x01) >> 0);
618                                 }
619                                 break;
620                         case FT_PIXEL_MODE_GRAY2:
621                                 for (x = 0; x < bmp->width; x += 4)
622                                 {
623                                         unsigned char ch = *src++;
624                                         *dst++ = ( ((ch & 0xA0) >> 6) * 0x55 ); ch <<= 2;
625                                         *dst++ = ( ((ch & 0xA0) >> 6) * 0x55 ); ch <<= 2;
626                                         *dst++ = ( ((ch & 0xA0) >> 6) * 0x55 ); ch <<= 2;
627                                         *dst++ = ( ((ch & 0xA0) >> 6) * 0x55 ); ch <<= 2;
628                                 }
629                                 break;
630                         case FT_PIXEL_MODE_GRAY4:
631                                 for (x = 0; x < bmp->width; x += 2)
632                                 {
633                                         unsigned char ch = *src++;
634                                         *dst++ = ( ((ch & 0xF0) >> 4) * 0x24);
635                                         *dst++ = ( ((ch & 0x0F) ) * 0x24);
636                                 }
637                                 break;
638                         default:
639                                 memcpy((void*)dst, (void*)src, bmp->pitch);
640                                 dst += bmp->pitch;
641                                 break;
642                         }
643                 }
644
645                 // now fill map->glyphs[ch - map->start]
646                 mapch = ch - map->start;
647                 mapglyph = &map->glyphs[mapch];
648                 // xmin is the left start of the character within the texture
649                 mapglyph->xmin = ( (double)(gC * font->glyphSize) ) / ( (double)(font->glyphSize * FONT_CHARS_PER_LINE) );
650                 mapglyph->xmax = mapglyph->xmin + glyph->metrics.width * (1.0/64.0) / (double)font->size;
651                 mapglyph->ymin = ( (double)(gR * font->glyphSize) ) / ( (double)(font->glyphSize * FONT_CHAR_LINES) );
652                 mapglyph->ymax = mapglyph->ymin + glyph->metrics.height * (1.0/64.0) / (double)font->size;
653                 // TODO: support vertical fonts maybe?
654                 mapglyph->advance_x = glyph->metrics.horiAdvance * (1.0/64.0) / (double)font->size;
655                 mapglyph->advance_y = 0;
656         }
657
658         // create a texture from the data now
659         dpsnprintf(map_identifier, sizeof(map_identifier), "%s_%u", font->name, (unsigned)map->start);
660         map->texture = R_LoadTexture2D(font_texturepool, map_identifier,
661                                        font->glyphSize * FONT_CHARS_PER_LINE,
662                                        font->glyphSize * FONT_CHAR_LINES,
663                                        data, TEXTYPE_RGBA, TEXF_ALPHA | TEXF_PRECACHE, NULL);
664         Mem_Free(data);
665         if (!map->texture)
666         {
667                 // if the first try isn't successful, keep it with a broken texture
668                 // otherwise we retry to load it every single frame where ft2 rendering is used
669                 // this would be bad...
670                 // only `data' must be freed
671                 return false;
672         }
673         if (outmap)
674                 *outmap = map;
675         return true;
676 }
677
678 extern void _DrawQ_Setup(void);
679
680 // TODO: If no additional stuff ends up in the following static functions
681 // use the DrawQ ones!
682 static void _Font_ProcessDrawFlag(int flags)
683 {
684         _DrawQ_Setup();
685         CHECKGLERROR
686         if(flags == DRAWFLAG_ADDITIVE)
687                 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
688         else if(flags == DRAWFLAG_MODULATE)
689                 GL_BlendFunc(GL_DST_COLOR, GL_ZERO);
690         else if(flags == DRAWFLAG_2XMODULATE)
691                 GL_BlendFunc(GL_DST_COLOR,GL_SRC_COLOR);
692         else if(flags == DRAWFLAG_SCREEN)
693                 GL_BlendFunc(GL_ONE_MINUS_DST_COLOR,GL_ONE);
694         else
695                 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
696 }
697
698 static void Font_GetTextColor(float color[4], int colorindex, float r, float g, float b, float a, qboolean shadow)
699 {
700         float C = r_textcontrast.value;
701         float B = r_textbrightness.value;
702         if (colorindex & 0x10000) // that bit means RGB color
703         {
704                 color[0] = ((colorindex >> 12) & 0xf) / 15.0;
705                 color[1] = ((colorindex >> 8) & 0xf) / 15.0;
706                 color[2] = ((colorindex >> 4) & 0xf) / 15.0;
707                 color[3] = (colorindex & 0xf) / 15.0;
708         }
709         else
710                 Vector4Copy(string_colors[colorindex], color);
711         Vector4Set(color, color[0] * r * C + B, color[1] * g * C + B, color[2] * b * C + B, color[3] * a);
712         if (shadow)
713         {
714                 float shadowalpha = (color[0]+color[1]+color[2]) * 0.8;
715                 Vector4Set(color, 0, 0, 0, color[3] * bound(0, shadowalpha, 1));
716         }
717 }
718
719 float Font_DrawString(float startx, float starty,
720                       const char *mbs, size_t maxlen,
721                       float basered, float basegreen, float baseblue, float basealpha,
722                       int flags, int *outcolor, qboolean ignorecolorcodes,
723                       const font_t *fnt)
724 {
725         int num, shadow, colorindex = STRING_COLOR_DEFAULT;
726         float x = startx, y, s, t, u, v, thisw;
727         float *av, *at, *ac;
728         float color[4];
729         int batchcount;
730         float vertex3f[QUADELEMENTS_MAXQUADS*4*3];
731         float texcoord2f[QUADELEMENTS_MAXQUADS*4*2];
732         float color4f[QUADELEMENTS_MAXQUADS*4*4];
733         Uchar ch;
734         size_t i;
735         const char *text_start;
736         int tempcolorindex;
737
738         int tw, th;
739         /*
740         tw = R_TextureWidth(fnt->tex);
741         th = R_TextureHeight(fnt->tex);
742         */
743
744         if (maxlen < 1)
745                 maxlen = 1<<30;
746
747         _DrawQ_ProcessDrawFlag(flags);
748
749         R_Mesh_ColorPointer(color4f, 0, 0);
750         R_Mesh_ResetTextureState();
751         R_Mesh_TexBind(0, R_GetTexture(fnt->tex));
752         R_Mesh_TexCoordPointer(0, 2, texcoord2f, 0, 0);
753         R_Mesh_VertexPointer(vertex3f, 0, 0);
754         R_SetupGenericShader(true);
755
756         ac = color4f;
757         at = texcoord2f;
758         av = vertex3f;
759         batchcount = 0;
760
761         for (shadow = r_textshadow.value != 0 && basealpha > 0;shadow >= 0;shadow--)
762         {
763                 text = text_start;
764                 if (!outcolor || *outcolor == -1)
765                         colorindex = STRING_COLOR_DEFAULT;
766                 else
767                         colorindex = *outcolor;
768
769                 Font_GetTextColor(color, colorindex, basered, basegreen, baseblue, basealpha, shadow);
770
771                 x = startx;
772                 y = starty;
773                 if (shadow)
774                 {
775                         x += r_textshadow.value;
776                         y += r_textshadow.value;
777                 }
778                 for (i = 0;i < maxlen && *text;i++)
779                 {
780                         if (*text == STRING_COLOR_TAG && !ignorecolorcodes && i + 1 < maxlen)
781                         {
782                                 const char *before;
783                                 Uchar chx[3];
784                                 ++text;
785                                 ch = *text; // the color tag is an ASCII character!
786                                 if (ch == STRING_COLOR_RGB_TAG_CHAR)
787                                 {
788                                         // we need some preparation here
789                                         before = text;
790                                         chx[2] = 0;
791                                         if (*text) chx[0] = u8_getchar(text, &text);
792                                         if (*text) chx[1] = u8_getchar(text, &text);
793                                         if (*text) chx[2] = u8_getchar(text, &text);
794                                         if ( ( (chx[0] >= 'A' && chx[0] <= 'F') && (chx[0] >= 'a' && chx[0] <= 'f') || (chx[0] >= '0' && chx[0] <= '9') ) &&
795                                              ( (chx[1] >= 'A' && chx[1] <= 'F') && (chx[1] >= 'a' && chx[1] <= 'f') || (chx[1] >= '0' && chx[1] <= '9') ) &&
796                                              ( (chx[2] >= 'A' && chx[2] <= 'F') && (chx[2] >= 'a' && chx[2] <= 'f') || (chx[2] >= '0' && chx[2] <= '9') ) )
797                                         {
798                                         }
799                                         else
800                                                 chx[2] = 0;
801                                         text = before; // start from the first hex character
802                                 }
803                                 if (ch <= '9' && ch >= '0') // ^[0-9] found
804                                 {
805                                         colorindex = ch - '0';
806                                         DrawQ_GetTextColor(color, colorindex, basered, basegreen, baseblue, basealpha, shadow);
807                                         continue;
808                                 }
809                                 else if (ch == STRING_COLOR_RGB_TAG_CHAR && i + 3 < maxlen && chx[2]) // ^x found
810                                 {
811                                         // building colorindex...
812                                         ch = tolower(text[i+1]);
813                                         tempcolorindex = 0x10000; // binary: 1,0000,0000,0000,0000
814                                         if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 12;
815                                         else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 12;
816                                         else tempcolorindex = 0;
817                                         if (tempcolorindex)
818                                         {
819                                                 ch = tolower(text[i+2]);
820                                                 if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 8;
821                                                 else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 8;
822                                                 else tempcolorindex = 0;
823                                                 if (tempcolorindex)
824                                                 {
825                                                         ch = tolower(text[i+3]);
826                                                         if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 4;
827                                                         else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 4;
828                                                         else tempcolorindex = 0;
829                                                         if (tempcolorindex)
830                                                         {
831                                                                 colorindex = tempcolorindex | 0xf;
832                                                                 // ...done! now colorindex has rgba codes (1,rrrr,gggg,bbbb,aaaa)
833                                                                 //Con_Printf("^1colorindex:^7 %x\n", colorindex);
834                                                                 DrawQ_GetTextColor(color, colorindex, basered, basegreen, baseblue, basealpha, shadow);
835                                                                 i+=3;
836                                                                 continue;
837                                                         }
838                                                 }
839                                         }
840                                 }
841                                 else if (ch == STRING_COLOR_TAG)
842                                         i++;
843                                 i--;
844                         }
845                         ch = u8_getchar(text, &text);
846                         
847                         font_map_t *map = fnt->font_map;
848                         while(map && map->start + FONT_CHARS_PER_MAP < ch)
849                                 map = map->next;
850                         if (!map)
851                         {
852                                 if (!Font_LoadMapForIndex(fnt, ch, &map))
853                                 {
854                                         shadow = -1;
855                                         break;
856                                 }
857                                 if (!map)
858                                 {
859                                         // this shouldn't happen
860                                         shadow = -1;
861                                         break;
862                                 }
863                         }
864                         
865                         tw = R_TextureWidth(map->texture);
866                         th = R_TextureHeight(map->texture);
867                         
868                         thisw = map->glyphslot[ch].advance_x;
869
870                         ac[ 0] = color[0];ac[ 1] = color[1];ac[ 2] = color[2];ac[ 3] = color[3];
871                         ac[ 4] = color[0];ac[ 5] = color[1];ac[ 6] = color[2];ac[ 7] = color[3];
872                         ac[ 8] = color[0];ac[ 9] = color[1];ac[10] = color[2];ac[11] = color[3];
873                         ac[12] = color[0];ac[13] = color[1];ac[14] = color[2];ac[15] = color[3];
874                         at[0] = map->glyphslot[ch].xmin; at[1] = map->glyphslot[ch].ymin;
875                         at[2] = map->glyphslot[ch].xmax; at[3] = map->glyphslot[ch].ymin;
876                         at[4] = map->glyphslot[ch].xmax; at[5] = map->glyphslot[ch].ymax;
877                         at[7] = map->glyphslot[ch].xmin; at[7] = map->glyphslot[ch].ymax;
878                         av[ 0] = x;           av[ 1] = y;     av[ 2] = 10;
879                         av[ 3] = x + w*thisw; av[ 4] = y;     av[ 5] = 10;
880                         av[ 6] = x + w*thisw; av[ 7] = y + h; av[ 8] = 10;
881                         av[ 9] = x;           av[10] = y + h; av[11] = 10;
882                         ac += 16;
883                         at += 8;
884                         av += 12;
885                         GL_LockArrays(0, 4);
886                         R_Mesh_Draw(0, 4, 0, 2, NULL, quadelements, 0, 0);
887                         GL_LockArrays(0, 0);
888                         x += thisw * w;
889                 }
890         }
891
892         if (outcolor)
893                 *outcolor = colorindex;
894
895         // note: this relies on the proper text (not shadow) being drawn last
896         return x;
897 }