r_font.c

Go to the documentation of this file.
00001 
00006 /*
00007 Copyright (C) 2002-2010 UFO: Alien Invasion.
00008 
00009 This program is free software; you can redistribute it and/or
00010 modify it under the terms of the GNU General Public License
00011 as published by the Free Software Foundation; either version 2
00012 of the License, or (at your option) any later version.
00013 
00014 This program is distributed in the hope that it will be useful,
00015 but WITHOUT ANY WARRANTY; without even the implied warranty of
00016 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
00017 
00018 See the GNU General Public License for more details.
00019 
00020 You should have received a copy of the GNU General Public License
00021 along with this program; if not, write to the Free Software
00022 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
00023 
00024 */
00025 
00026 #include "r_local.h"
00027 #include "r_font.h"
00028 #include "r_error.h"
00029 #include "../../shared/utf8.h"
00030 
00031 #define MAX_CACHE_STRING    128
00032 #define MAX_CHUNK_CACHE     1024 /* making this bigger uses more GL textures */
00033 #define MAX_WRAP_CACHE      1024 /* making this bigger uses more memory */
00034 #define MAX_WRAP_HASH       4096 /* making this bigger reduces collisions */
00035 #define MAX_FONTS           16
00036 #define MAX_FONTNAME        32
00037 #define MAX_TRUNCMARKER     16   /* enough for 3 chinese chars */
00038 
00039 #define BUF_SIZE 2048
00040 
00048 typedef struct {
00049     int pos;        
00050     int len;        
00051     int linenum;    
00052     int width;      
00053     /* no need for individual line height, just use font->height */
00054     qboolean truncated; 
00055     vec2_t texsize; 
00056     GLuint texnum;  
00057 } chunkCache_t;
00058 
00069 typedef struct wrapCache_s {
00070     char text[MAX_CACHE_STRING];    
00071     const font_t *font; 
00072     struct wrapCache_s *next;       
00073     int maxWidth;       
00074     longlines_t method;     
00075     int numChunks;      
00076     int numLines;       
00077     int chunkIdx;       
00078     qboolean aborted;   
00079 } wrapCache_t;
00080 
00081 static int numFonts = 0;
00082 static font_t fonts[MAX_FONTS];
00083 
00084 static chunkCache_t chunkCache[MAX_CHUNK_CACHE];
00085 static wrapCache_t wrapCache[MAX_WRAP_CACHE];
00086 static wrapCache_t *hash[MAX_WRAP_HASH];
00087 static int numChunks = 0;
00088 static int numWraps = 0;
00089 
00095 static char truncmarker[MAX_TRUNCMARKER] =  "...";
00096 
00097 typedef struct {
00098     const char *name;
00099     int renderStyle;
00100 } fontRenderStyle_t;
00101 
00102 #define NUM_FONT_STYLES (sizeof(fontStyle) / sizeof(fontRenderStyle_t))
00103 static const fontRenderStyle_t fontStyle[] = {
00104     {"TTF_STYLE_NORMAL", TTF_STYLE_NORMAL},
00105     {"TTF_STYLE_BOLD", TTF_STYLE_BOLD},
00106     {"TTF_STYLE_ITALIC", TTF_STYLE_ITALIC},
00107     {"TTF_STYLE_UNDERLINE", TTF_STYLE_UNDERLINE}
00108 };
00109 
00110 /*============================================================== */
00111 
00112 void R_FontSetTruncationMarker (const char *marker)
00113 {
00114     Q_strncpyz(truncmarker, marker, sizeof(truncmarker));
00115 }
00116 
00117 
00121 static void R_FontCleanCache (void)
00122 {
00123     int i;
00124 
00125     R_CheckError();
00126 
00127     /* free the surfaces */
00128     for (i = 0; i < numChunks; i++) {
00129         if (!chunkCache[i].texnum)
00130             continue;
00131         glDeleteTextures(1, &(chunkCache[i].texnum));
00132         R_CheckError();
00133     }
00134 
00135     memset(chunkCache, 0, sizeof(chunkCache));
00136     memset(wrapCache, 0, sizeof(wrapCache));
00137     memset(hash, 0, sizeof(hash));
00138     numChunks = 0;
00139     numWraps = 0;
00140 }
00141 
00146 void R_FontShutdown (void)
00147 {
00148     int i;
00149 
00150     R_FontCleanCache();
00151 
00152     for (i = 0; i < numFonts; i++)
00153         if (fonts[i].font) {
00154             TTF_CloseFont(fonts[i].font);
00155             FS_FreeFile(fonts[i].buffer);
00156             SDL_RWclose(fonts[i].rw);
00157         }
00158 
00159     memset(fonts, 0, sizeof(fonts));
00160     numFonts = 0;
00161 
00162     /* now quit SDL_ttf, too */
00163     TTF_Quit();
00164 }
00165 
00169 static font_t *R_FontAnalyze (const char *name, const char *path, int renderStyle, int size)
00170 {
00171     font_t *f;
00172     int ttfSize;
00173 
00174     if (numFonts >= MAX_FONTS)
00175         return NULL;
00176 
00177     /* allocate new font */
00178     f = &fonts[numFonts];
00179     memset(f, 0, sizeof(*f));
00180 
00181     /* copy fontname */
00182     f->name = name;
00183 
00184     ttfSize = FS_LoadFile(path, &f->buffer);
00185 
00186     f->rw = SDL_RWFromMem(f->buffer, ttfSize);
00187 
00188     f->font = TTF_OpenFontRW(f->rw, 0, size);
00189     if (!f->font)
00190         Com_Error(ERR_FATAL, "...could not load font file %s", path);
00191 
00192     /* font style */
00193     f->style = renderStyle;
00194     if (f->style)
00195         TTF_SetFontStyle(f->font, f->style);
00196 
00197     numFonts++;
00198     f->lineSkip = TTF_FontLineSkip(f->font);
00199     f->height = TTF_FontHeight(f->font);
00200 
00201     /* return the font */
00202     return f;
00203 }
00204 
00208 font_t *R_GetFont (const char *name)
00209 {
00210     int i;
00211 
00212     for (i = 0; i < numFonts; i++)
00213         if (!strcmp(name, fonts[i].name))
00214             return &fonts[i];
00215 
00216     Com_Error(ERR_FATAL, "Could not find font: %s", name);
00217 }
00218 
00219 
00223 void R_FontListCache_f (void)
00224 {
00225     int i;
00226     int collSum = 0;
00227 
00228     Com_Printf("Font cache info\n========================\n");
00229     Com_Printf("...wrap cache size: %i - used %i\n", MAX_WRAP_CACHE, numWraps);
00230     Com_Printf("...chunk cache size: %i - used %i\n", MAX_CHUNK_CACHE, numChunks);
00231 
00232     for (i = 0; i < numWraps; i++) {
00233         const wrapCache_t *wrap = &wrapCache[i];
00234         int collCount = 0;
00235         while (wrap->next) {
00236             collCount++;
00237             wrap = wrap->next;
00238         }
00239         if (collCount)
00240             Com_Printf("...%i collisions for %s\n", collCount, wrap->text);
00241         collSum += collCount;
00242     }
00243     Com_Printf("...overall collisions %i\n", collSum);
00244 }
00245 
00250 static int R_FontHash (const char *string)
00251 {
00252     register int hashValue, i;
00253 
00254     hashValue = 0;
00255     for (i = 0; string[i] != '\0'; i++)
00256         hashValue += string[i] * (119 + i);
00257 
00258     hashValue = (hashValue ^ (hashValue >> 10) ^ (hashValue >> 20));
00259     return hashValue & (MAX_WRAP_HASH - 1);
00260 }
00261 
00266 static int R_FontChunkLength (const font_t *f, char *text, int len)
00267 {
00268     int width;
00269     char old;
00270 
00271     if (len == 0)
00272         return 0;
00273 
00274     old = text[len];
00275     text[len] = '\0';
00276     TTF_SizeUTF8(f->font, text, &width, NULL);
00277     text[len] = old;
00278 
00279     return width;
00280 }
00281 
00290 static int R_FontFindFit (const font_t *f, char *text, int maxlen, int maxWidth, int *widthp)
00291 {
00292     int bestbreak = 0;
00293     int width;
00294     int len;
00295 
00296     *widthp = 0;
00297 
00298     /* Fit whole words */
00299     for (len = 1; len < maxlen; len++) {
00300         if (text[len] == ' ') {
00301             width = R_FontChunkLength(f, text, len);
00302             if (width > maxWidth)
00303                 break;
00304             bestbreak = len;
00305             *widthp = width;
00306         }
00307     }
00308 
00309     /* Fit hyphenated word parts */
00310     for (len = bestbreak + 1; len < maxlen; len++) {
00311         if (text[len] == '-') {
00312             width = R_FontChunkLength(f, text, len + 1);
00313             if (width > maxWidth)
00314                 break;
00315             bestbreak = len + 1;
00316             *widthp = width;
00317         }
00318     }
00319 
00320     if (bestbreak > 0)
00321         return bestbreak;
00322 
00325     /* Can't fit even one word. Break first word anywhere. */
00326     for (len = 1; len < maxlen; len++) {
00327         if (UTF8_CONTINUATION_BYTE(text[len]))
00328             continue;
00329         width = R_FontChunkLength(f, text, len);
00330         if (width > maxWidth)
00331             break;
00332         bestbreak = len;
00333         *widthp = width;
00334     }
00335 
00336     return bestbreak;
00337 }
00338 
00345 static int R_FontFindTruncFit (const font_t *f, const char *text, int maxlen, int maxWidth, qboolean mark, int *widthp)
00346 {
00347     char buf[BUF_SIZE];
00348     int width;
00349     int len;
00350     int breaklen;
00351 
00352     breaklen = 0;
00353     *widthp = 0;
00354 
00355     for (len = 1; len < maxlen; len++) {
00356         buf[len - 1] = text[len - 1];
00357         if (UTF8_CONTINUATION_BYTE(text[len]))
00358             continue;
00359         if (mark)
00360             Q_strncpyz(&buf[len], truncmarker, sizeof(buf) - len);
00361         else
00362             buf[len] = '\0';
00363         TTF_SizeUTF8(f->font, buf, &width, NULL);
00364         if (width > maxWidth)
00365             return breaklen;
00366         breaklen = len;
00367         *widthp = width;
00368     }
00369 
00370     return maxlen;
00371 }
00372 
00378 static int R_FontMakeChunks (const font_t *f, const char *text, int maxWidth, longlines_t method, int *lines, qboolean *aborted)
00379 {
00380     int lineno = 0;
00381     int pos = 0;
00382     int startChunks = numChunks;
00383     char buf[BUF_SIZE];
00384 
00385     assert(text);
00386 
00387     Q_strncpyz(buf, text, sizeof(buf));
00388 
00389     do {
00390         int width;
00391         int len;
00392         int utf8len;
00393         int skip = 0;
00394         qboolean truncated = qfalse;
00395 
00396         /* find mandatory break */
00397         len = strcspn(&buf[pos], "\n");
00398 
00399         /* tidy up broken UTF-8 at end of line which may have been
00400          * truncated by caller by use of functions like Q_strncpyz */
00401         utf8len = 1;
00402         while (len > utf8len && UTF8_CONTINUATION_BYTE(buf[pos + len - utf8len]))
00403             utf8len++;
00404         if (len > 0 && utf8len != UTF8_char_len(buf[pos + len - utf8len])) {
00405             len -= utf8len;
00406             skip += utf8len;
00407         }
00408 
00409         /* delete trailing spaces */
00410         while (len > 0 && buf[pos + len - 1] == ' ') {
00411             len--;
00412             skip++;
00413         }
00414 
00415         width = R_FontChunkLength(f, &buf[pos], len);
00416         if (maxWidth > 0 && width > maxWidth) {
00417             if (method == LONGLINES_WRAP) {
00418                 /* full chunk didn't fit; try smaller */
00419                 len = R_FontFindFit(f, &buf[pos], len, maxWidth, &width);
00420                 /* skip following spaces */
00421                 skip = 0;
00422                 while (buf[pos + len + skip] == ' ')
00423                     skip++;
00424                 if (len + skip == 0) {
00425                     *aborted = qtrue;
00426                     break; /* could not fit even one character */
00427                 }
00428             } else {
00429                 truncated = (method == LONGLINES_PRETTYCHOP);
00430                 len = R_FontFindTruncFit(f, &buf[pos], len, maxWidth, truncated, &width);
00431                 skip = strcspn(&buf[pos + len], "\n");
00432             }
00433         }
00434         if (width > 0) {
00435             /* add chunk to cache */
00436             if (numChunks >= MAX_CHUNK_CACHE) {
00437                 /* whoops, ran out of cache, wipe cache and start over */
00438                 R_FontCleanCache();
00440                 return R_FontMakeChunks(f, text, maxWidth, method, lines, aborted);
00441             }
00442             chunkCache[numChunks].pos = pos;
00443             chunkCache[numChunks].len = len;
00444             chunkCache[numChunks].linenum = lineno;
00445             chunkCache[numChunks].width = width;
00446             chunkCache[numChunks].truncated = truncated;
00447             numChunks++;
00448         }
00449         pos += len + skip;
00450         if (buf[pos] == '\n' || buf[pos] == '\\') {
00451             pos++;
00452         }
00453         lineno++;
00454     } while (buf[pos] != '\0');
00455 
00456     /* If there were empty lines at the end of the text, then lineno might
00457      * be greater than the linenum of the last chunk. Some callers need to
00458      * know this to count lines accurately. */
00459     *lines = lineno;
00460     return numChunks - startChunks;
00461 }
00462 
00467 static wrapCache_t *R_FontWrapText (const font_t *f, const char *text, int maxWidth, longlines_t method)
00468 {
00469     wrapCache_t *wrap;
00470     int hashValue = R_FontHash(text);
00471     int chunksUsed;
00472     int lines;
00473     qboolean aborted = qfalse;
00474 
00475     /* String is considered a match if the part that fit in entry->string
00476      * matches. Since the hash value also matches and the hash was taken
00477      * over the whole string, this is good enough. */
00478     for (wrap = hash[hashValue]; wrap; wrap = wrap->next)
00479         /* big string are cut, we must not test the 256e character ('\0') */
00480         if (!strncmp(text, wrap->text, sizeof(wrap->text) - 1)
00481          && wrap->font == f
00482          && wrap->method == method
00483          && (wrap->maxWidth == maxWidth
00484             || (wrap->numLines == 1 && !wrap->aborted
00485                 && !chunkCache[wrap->chunkIdx].truncated
00486                 && (chunkCache[wrap->chunkIdx].width <= maxWidth || maxWidth <= 0))))
00487             return wrap;
00488 
00489     if (numWraps >= MAX_WRAP_CACHE)
00490         R_FontCleanCache();
00491 
00492     /* It is possible that R_FontMakeChunks will wipe the cache,
00493      * so do not rely on numWraps until it completes. */
00494     chunksUsed = R_FontMakeChunks(f, text, maxWidth, method, &lines, &aborted);
00495 
00496     wrap = &wrapCache[numWraps];
00497     strncpy(wrap->text, text, sizeof(wrap->text));
00498     wrap->text[sizeof(wrap->text) - 1] = '\0';
00499     wrap->font = f;
00500     wrap->maxWidth = maxWidth;
00501     wrap->method = method;
00502     wrap->aborted = aborted;
00503     wrap->numChunks = chunksUsed;
00504     wrap->numLines = lines;
00505     wrap->chunkIdx = numChunks - chunksUsed;
00506 
00507     /* insert new text into wrap cache */
00508     wrap->next = hash[hashValue];
00509     hash[hashValue] = wrap;
00510     numWraps++;
00511 
00512     return wrap;
00513 }
00514 
00523 void R_FontTextSize (const char *fontId, const char *text, int maxWidth, longlines_t method, int *width, int *height, int *lines, qboolean *isTruncated)
00524 {
00525     const font_t *font = R_GetFont(fontId);
00526     const wrapCache_t *wrap = R_FontWrapText(font, text, maxWidth, method);
00527 
00528     if (width) {
00529         int i;
00530         *width = 0;
00531         for (i = 0; i < wrap->numChunks; i++) {
00532             if (chunkCache[wrap->chunkIdx + i].width > *width)
00533                 *width = chunkCache[wrap->chunkIdx + i].width;
00534         }
00535     }
00536 
00537     if (height)
00538         *height = (wrap->numLines - 1) * font->lineSkip + font->height;
00539 
00540     if (lines)
00541         *lines = wrap->numLines;
00542 
00543     if (isTruncated)
00544         *isTruncated = chunkCache[wrap->chunkIdx].truncated;
00545 }
00546 
00555 static void R_FontGenerateTexture (const font_t *font, const char *text, chunkCache_t *chunk)
00556 {
00557     int w, h;
00558     SDL_Surface *textSurface;
00559     SDL_Surface *openGLSurface;
00560     SDL_Rect rect = {0, 0, 0, 0};
00561     char buf[BUF_SIZE];
00562     static const SDL_Color color = {255, 255, 255, 0};  /* The 4th value is unused */
00563     const int samples = r_config.gl_compressed_alpha_format ? r_config.gl_compressed_alpha_format : r_config.gl_alpha_format;
00564 
00565 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
00566     Uint32 rmask = 0xff000000;
00567     Uint32 gmask = 0x00ff0000;
00568     Uint32 bmask = 0x0000ff00;
00569     Uint32 amask = 0x000000ff;
00570 #else
00571     Uint32 rmask = 0x000000ff;
00572     Uint32 gmask = 0x0000ff00;
00573     Uint32 bmask = 0x00ff0000;
00574     Uint32 amask = 0xff000000;
00575 #endif
00576 
00577     if (chunk->texnum != 0)
00578         return;  /* already generated */
00579 
00580     assert(strlen(text) >= chunk->pos + chunk->len);
00581     if (chunk->len >= sizeof(buf))
00582         return;
00583     memcpy(buf, &text[chunk->pos], chunk->len);
00584     buf[chunk->len] = 0;
00585 
00586     if (chunk->truncated)
00587         Q_strncpyz(buf + chunk->len, truncmarker, sizeof(buf) - chunk->len);
00588 
00589     textSurface = TTF_RenderUTF8_Blended(font->font, buf, color);
00590     if (!textSurface) {
00591         Com_Printf("%s (%s)\n", TTF_GetError(), buf);
00592         return;
00593     }
00594 
00595     /* copy text to a surface of suitable size for a texture (power of two) */
00596     for (w = 2; w < textSurface->w; w <<= 1) {}
00597     for (h = 2; h < textSurface->h; h <<= 1) {}
00598 
00599     openGLSurface = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 32, rmask, gmask, bmask, amask);
00600     if (!openGLSurface)
00601         return;
00602 
00603     rect.x = rect.y = 0;
00604     rect.w = textSurface->w;
00605     if (rect.w > chunk->width)
00606         rect.w = chunk->width;
00607     rect.h = textSurface->h;
00608 
00609     /* ignore alpha when blitting - just copy it over */
00610     SDL_SetAlpha(textSurface, 0, 255);
00611 
00612     SDL_LowerBlit(textSurface, &rect, openGLSurface, &rect);
00613     SDL_FreeSurface(textSurface);
00614 
00615     /* use a fixed texture number allocation scheme */
00616     chunk->texnum = TEXNUM_FONTS + (chunk - chunkCache);
00617     R_BindTexture(chunk->texnum);
00618     glTexImage2D(GL_TEXTURE_2D, 0, samples, w, h, 0, GL_BGRA, GL_UNSIGNED_BYTE, openGLSurface->pixels);
00619     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
00620     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
00621     Vector2Set(chunk->texsize, w, h);
00622     R_CheckError();
00623     SDL_FreeSurface(openGLSurface);
00624 }
00625 
00626 static const float font_texcoords[] = {
00627     0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0
00628 };
00629 
00630 static void R_FontDrawTexture (int texId, int x, int y, int w, int h)
00631 {
00632     const float nx = x * viddef.rx;
00633     const float ny = y * viddef.ry;
00634     const float nw = w * viddef.rx;
00635     const float nh = h * viddef.ry;
00636 
00637     R_BindTexture(texId);
00638 
00639     glVertexPointer(2, GL_SHORT, 0, r_state.vertex_array_2d);
00640 
00641     memcpy(&texunit_diffuse.texcoord_array, font_texcoords, sizeof(font_texcoords));
00642 
00643     r_state.vertex_array_2d[0] = nx;
00644     r_state.vertex_array_2d[1] = ny;
00645     r_state.vertex_array_2d[2] = nx + nw;
00646     r_state.vertex_array_2d[3] = ny;
00647 
00648     r_state.vertex_array_2d[4] = nx;
00649     r_state.vertex_array_2d[5] = ny + nh;
00650     r_state.vertex_array_2d[6] = nx + nw;
00651     r_state.vertex_array_2d[7] = ny + nh;
00652 
00653     glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
00654 
00655     /* set back to standard 3d pointer */
00656     glVertexPointer(3, GL_FLOAT, 0, r_state.vertex_array_3d);
00657 }
00658 
00675 int R_FontDrawString (const char *fontId, align_t align, int x, int y, int absX, int maxWidth,
00676         int lineHeight, const char *c, int boxHeight, int scrollPos, int *curLine, longlines_t method)
00677 {
00678     const font_t *font = R_GetFont(fontId);
00679     const wrapCache_t *wrap;
00680     int i;
00681     const align_t horizontalAlign = align % 3; /* left, center, right */
00682     int xalign = 0;
00683 
00684     wrap = R_FontWrapText(font, c, maxWidth - (x - absX), method);
00685 
00686     if (boxHeight <= 0)
00687         boxHeight = wrap->numLines;
00688 
00689     for (i = 0; i < wrap->numChunks; i++) {
00690         chunkCache_t *chunk = &chunkCache[wrap->chunkIdx + i];
00691         int linenum = chunk->linenum;
00692 
00693         if (curLine)
00694             linenum += *curLine;
00695 
00696         if (horizontalAlign == 1)
00697             xalign = -(chunk->width / 2);
00698         else if (horizontalAlign == 2)
00699             xalign = -chunk->width;
00700         else
00701             xalign = 0;
00702 
00703         if (linenum < scrollPos || linenum >= scrollPos + boxHeight)
00704             continue;
00705 
00706         R_FontGenerateTexture(font, c, chunk);
00707         R_FontDrawTexture(chunk->texnum, x + xalign, y + (linenum - scrollPos) * lineHeight, chunk->texsize[0], chunk->texsize[1]);
00708     }
00709 
00710     return wrap->numLines;
00711 }
00712 
00713 void R_FontInit (void)
00714 {
00715 #ifdef SDL_TTF_VERSION
00716     SDL_version version;
00717 
00718     SDL_TTF_VERSION(&version)
00719     Com_Printf("SDL_ttf version %i.%i.%i - we need at least 2.0.7\n",
00720         version.major,
00721         version.minor,
00722         version.patch);
00723 #else
00724     Com_Printf("could not get SDL_ttf version - we need at least 2.0.7\n");
00725 #endif
00726 
00727     numFonts = 0;
00728     memset(fonts, 0, sizeof(fonts));
00729 
00730     memset(chunkCache, 0, sizeof(chunkCache));
00731     memset(wrapCache, 0, sizeof(wrapCache));
00732     memset(hash, 0, sizeof(hash));
00733     numChunks = 0;
00734     numWraps = 0;
00735 
00736     /* init the truetype font engine */
00737     if (TTF_Init() == -1)
00738         Com_Error(ERR_FATAL, "SDL_ttf error: %s", TTF_GetError());
00739 }
00740 
00741 void R_FontRegister (const char *name, int size, const char *path, const char *style)
00742 {
00743     int renderstyle = TTF_STYLE_NORMAL;     /* NORMAL is standard */
00744 
00745     if (style && style[0] != '\0') {
00746         int i;
00747         for (i = 0; i < NUM_FONT_STYLES; i++)
00748             if (!Q_strcasecmp(fontStyle[i].name, style)) {
00749                 renderstyle = fontStyle[i].renderStyle;
00750                 break;
00751             }
00752     }
00753 
00754     R_FontAnalyze(name, path, renderstyle, size);
00755 }

Generated by  doxygen 1.6.2