00001
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
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
00033 #define MAX_WRAP_CACHE 1024
00034 #define MAX_WRAP_HASH 4096
00035 #define MAX_FONTS 16
00036 #define MAX_FONTNAME 32
00037 #define MAX_TRUNCMARKER 16
00038
00039 #define BUF_SIZE 2048
00040
00048 typedef struct {
00049 int pos;
00050 int len;
00051 int linenum;
00052 int width;
00053
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
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
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
00178 f = &fonts[numFonts];
00179 memset(f, 0, sizeof(*f));
00180
00181
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
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
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
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
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
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
00397 len = strcspn(&buf[pos], "\n");
00398
00399
00400
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
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
00419 len = R_FontFindFit(f, &buf[pos], len, maxWidth, &width);
00420
00421 skip = 0;
00422 while (buf[pos + len + skip] == ' ')
00423 skip++;
00424 if (len + skip == 0) {
00425 *aborted = qtrue;
00426 break;
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
00436 if (numChunks >= MAX_CHUNK_CACHE) {
00437
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
00457
00458
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
00476
00477
00478 for (wrap = hash[hashValue]; wrap; wrap = wrap->next)
00479
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
00493
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
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};
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;
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
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
00610 SDL_SetAlpha(textSurface, 0, 255);
00611
00612 SDL_LowerBlit(textSurface, &rect, openGLSurface, &rect);
00613 SDL_FreeSurface(textSurface);
00614
00615
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
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;
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
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;
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 }