1 /**
2  * @file
3  * @brief font handling with SDL_ttf font engine
4  */
5 
6 /*
7 Copyright (C) 2002-2013 UFO: Alien Invasion.
8 
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License
11 as published by the Free Software Foundation; either version 2
12 of the License, or (at your option) any later version.
13 
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17 
18 See the GNU General Public License for more details.
19 
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
23 
24 */
25 
26 #include "r_local.h"
27 #include "r_font.h"
28 #include "r_error.h"
29 #include "../../shared/utf8.h"
30 
31 #define MAX_CACHE_STRING	128
32 #define MAX_CHUNK_CACHE		1024 /* making this bigger uses more GL textures */
33 #define MAX_WRAP_CACHE		1024 /* making this bigger uses more memory */
34 #define MAX_WRAP_HASH		4096 /* making this bigger reduces collisions */
35 #define MAX_FONTS			16
36 #define MAX_FONTNAME		32
37 #define MAX_TRUNCMARKER		16   /* enough for 3 chinese chars */
38 
39 #define BUF_SIZE 4096
40 
41 /**
42  * @brief This structure holds one piece of text (usually a whole line)
43  * and the texture on which it is rendered. It also holds positioning
44  * information about the place of this piece in a multiline text.
45  * Further information is held in the wrapCache_t struct that points
46  * to this struct.
47  */
48 typedef struct {
49 	int pos;		/**< offset of this chunk in source string */
50 	int len;		/**< length of this chunk in source string */
51 	int linenum;	/**< 0-based line offset from first line of text */
52 	int width;		/**< text chunk rendered width in pixels */
53 	/* no need for individual line height, just use font->height */
54 	bool truncated;	/**< needs ellipsis after text */
55 	vec2_t texsize;	/**< texture width and height */
56 	GLuint texnum;	/**< bound texture ID (0 if not textured yet) */
57 } chunkCache_t;
58 
59 /**
60  * @brief This structure caches information about rendering a text
61  * in one font wrapped to a specific width. It points to structures
62  * in the chunkCache that cache detailed information and the textures used.
63  *
64  * @note Caching text-wrapping information is particularly important
65  * for Cyrillic and possibly other non-ascii text, where TTF_SizeUTF8()
66  * is almost as slow as rendering. Intro sequence went from 4 fps to 50
67  * after introducing the wrapCache.
68  */
69 typedef struct wrapCache_s {
70 	char text[MAX_CACHE_STRING];	/**< hash id */
71 	const font_t* font;	/**< font used for wrapping/rendering this text */
72 	struct wrapCache_s* next;		/**< next hash entry in case of collision */
73 	int maxWidth;		/**< width to which this text was wrapped */
74 	longlines_t method;		/**< were long lines wrapped or truncated? */
75 	int numChunks;		/**< number of (contiguous) chunks in chunkCache used */
76 	int numLines;		/**< total line count of wrapped text */
77 	int chunkIdx;		/**< first chunk in chunkCache for this text */
78 	bool aborted;	/**< true if we can't finish the chunk generation */
79 } wrapCache_t;
80 
81 static int numFonts = 0;
82 static font_t fonts[MAX_FONTS];
83 
84 static chunkCache_t chunkCache[MAX_CHUNK_CACHE];
85 static wrapCache_t wrapCache[MAX_WRAP_CACHE];
86 static wrapCache_t* hash[MAX_WRAP_HASH];
87 static int numChunks = 0;
88 static int numWraps = 0;
89 
90 /**
91  * @brief This string is added at the end of truncated strings.
92  * By default it is an ellipsis, but the caller can change that.
93  * @sa R_FontSetTruncationMarker
94  */
95 static char truncmarker[MAX_TRUNCMARKER] =  "...";
96 
97 typedef struct {
98 	const char* name;
99 	int renderStyle;
100 } fontRenderStyle_t;
101 
102 #define NUM_FONT_STYLES (sizeof(fontStyle) / sizeof(fontRenderStyle_t))
103 static const fontRenderStyle_t fontStyle[] = {
104 	{"TTF_STYLE_NORMAL", TTF_STYLE_NORMAL},
105 	{"TTF_STYLE_BOLD", TTF_STYLE_BOLD},
106 	{"TTF_STYLE_ITALIC", TTF_STYLE_ITALIC},
107 	{"TTF_STYLE_UNDERLINE", TTF_STYLE_UNDERLINE}
108 };
109 
110 /*============================================================== */
111 
R_FontSetTruncationMarker(const char * marker)112 void R_FontSetTruncationMarker (const char* marker)
113 {
114 	Q_strncpyz(truncmarker, marker, sizeof(truncmarker));
115 }
116 
117 
118 /**
119  * @brief Clears font cache and frees memory associated with the cache
120  */
R_FontCleanCache(void)121 void R_FontCleanCache (void)
122 {
123 	int i;
124 
125 	R_CheckError();
126 
127 	/* free the surfaces */
128 	for (i = 0; i < numChunks; i++) {
129 		if (!chunkCache[i].texnum)
130 			continue;
131 		glDeleteTextures(1, &(chunkCache[i].texnum));
132 		R_CheckError();
133 	}
134 
135 	OBJZERO(chunkCache);
136 	OBJZERO(wrapCache);
137 	OBJZERO(hash);
138 	numChunks = 0;
139 	numWraps = 0;
140 }
141 
142 /**
143  * @brief frees the SDL_ttf fonts
144  * @sa R_FontCleanCache
145  */
R_FontShutdown(void)146 void R_FontShutdown (void)
147 {
148 	int i;
149 
150 	R_FontCleanCache();
151 
152 	for (i = 0; i < numFonts; i++)
153 		if (fonts[i].font) {
154 			TTF_CloseFont(fonts[i].font);
155 			Mem_Free(fonts[i].buffer);
156 			SDL_RWclose(fonts[i].rw);
157 		}
158 
159 	OBJZERO(fonts);
160 	numFonts = 0;
161 
162 	/* now quit SDL_ttf, too */
163 	TTF_Quit();
164 }
165 
166 /**
167  * @todo Check whether font is already loaded
168  */
R_FontAnalyze(const char * name,const char * path,int renderStyle,int size)169 static font_t* R_FontAnalyze (const char* name, const char* path, int renderStyle, int size)
170 {
171 	font_t* f;
172 	int ttfSize;
173 
174 	if (numFonts >= MAX_FONTS)
175 		return nullptr;
176 
177 	/* allocate new font */
178 	f = &fonts[numFonts];
179 	OBJZERO(*f);
180 
181 	/* copy fontname */
182 	f->name = name;
183 
184 	byte* buf;
185 	ttfSize = FS_LoadFile(path, &buf);
186 	if (ttfSize == -1)
187 		Com_Error(ERR_FATAL, "...could not load font file %s", path);
188 
189 	/* duplicate the memory to survive a filesystem restart */
190 	f->buffer = Mem_Dup(byte, buf, ttfSize);
191 	f->rw = SDL_RWFromMem(f->buffer, ttfSize);
192 
193 	f->font = TTF_OpenFontRW(f->rw, 0, size);
194 	if (!f->font)
195 		Com_Error(ERR_FATAL, "...could not load ttf font data %s (%s)", path, TTF_GetError());
196 
197 	/* font style */
198 	f->style = renderStyle;
199 	if (f->style)
200 		TTF_SetFontStyle(f->font, f->style);
201 
202 	numFonts++;
203 	f->lineSkip = TTF_FontLineSkip(f->font);
204 	f->height = TTF_FontHeight(f->font);
205 
206 	/* return the font */
207 	return f;
208 }
209 
210 /**
211  * @brief Searches the array of available fonts (see fonts.ufo)
212  */
R_GetFont(const char * name)213 font_t* R_GetFont (const char* name)
214 {
215 	int i;
216 
217 	for (i = 0; i < numFonts; i++)
218 		if (Q_streq(name, fonts[i].name))
219 			return &fonts[i];
220 
221 #ifndef COMPILE_UNITTESTS
222 	Com_Error(ERR_FATAL, "Could not find font: %s", name);
223 #else
224 	Com_Printf("R_GetFont: Could not find font: %s. Return nullptr\n", name);
225 	return nullptr;
226 #endif
227 }
228 
229 
230 /**
231  * @brief Console command binding to show the font cache
232  */
R_FontListCache_f(void)233 void R_FontListCache_f (void)
234 {
235 	int i;
236 	int collSum = 0;
237 
238 	Com_Printf("Font cache info\n========================\n");
239 	Com_Printf("...wrap cache size: %i - used %i\n", MAX_WRAP_CACHE, numWraps);
240 	Com_Printf("...chunk cache size: %i - used %i\n", MAX_CHUNK_CACHE, numChunks);
241 
242 	for (i = 0; i < numWraps; i++) {
243 		const wrapCache_t* wrap = &wrapCache[i];
244 		int collCount = 0;
245 		while (wrap->next) {
246 			collCount++;
247 			wrap = wrap->next;
248 		}
249 		if (collCount)
250 			Com_Printf("...%i collisions for %s\n", collCount, wrap->text);
251 		collSum += collCount;
252 	}
253 	Com_Printf("...overall collisions %i\n", collSum);
254 }
255 
256 /**
257  * @param[in] string String to build the hash value for
258  * @return hash value for given string
259  */
R_FontHash(const char * string,const font_t * font)260 static int R_FontHash (const char* string, const font_t* font)
261 {
262 	register int hashValue, i;
263 
264 	hashValue = 0x2040189 * ((font - fonts) + 1);
265 	for (i = 0; string[i] != '\0'; i++)
266 		hashValue = (hashValue + string[i]) * 16777619 + 1;
267 
268 	hashValue = (hashValue ^ (hashValue >> 10) ^ (hashValue >> 20));
269 	return hashValue & (MAX_WRAP_HASH - 1);
270 }
271 
272 /**
273  * @brief Calculate the width in pixels needed to render a piece of text.
274  * Can temporarily modify the caller's string but leaves it unchanged.
275  */
R_FontChunkLength(const font_t * f,char * text,int len)276 static int R_FontChunkLength (const font_t* f, char* text, int len)
277 {
278 	int width;
279 	char old;
280 
281 	if (len == 0)
282 		return 0;
283 
284 	old = text[len];
285 	text[len] = '\0';
286 	TTF_SizeUTF8(f->font, text, &width, nullptr);
287 	text[len] = old;
288 
289 	return width;
290 }
291 
292 /**
293  * @brief Find longest part of text that fits in maxWidth pixels,
294  * with a clean break such as at a word boundary.
295  * Can temporarily modify the caller's string but leaves it unchanged.
296  * Assumes whole string won't fit.
297  * @param[out] widthp Pixel width of part that fits.
298  * @return String length of part that fits.
299  */
R_FontFindFit(const font_t * f,char * text,int maxlen,int maxWidth,int * widthp)300 static int R_FontFindFit (const font_t* f, char* text, int maxlen, int maxWidth, int* widthp)
301 {
302 	int bestbreak = 0;
303 	int width;
304 	int len;
305 
306 	*widthp = 0;
307 
308 	/* Fit whole words */
309 	for (len = 1; len < maxlen; len++) {
310 		if (text[len] == ' ') {
311 			width = R_FontChunkLength(f, text, len);
312 			if (width > maxWidth)
313 				break;
314 			bestbreak = len;
315 			*widthp = width;
316 		}
317 	}
318 
319 	/* Fit hyphenated word parts */
320 	for (len = bestbreak + 1; len < maxlen; len++) {
321 		if (text[len] == '-') {
322 			width = R_FontChunkLength(f, text, len + 1);
323 			if (width > maxWidth)
324 				break;
325 			bestbreak = len + 1;
326 			*widthp = width;
327 		}
328 	}
329 
330 	if (bestbreak > 0)
331 		return bestbreak;
332 
333 	/** @todo Smart breaking of Chinese text */
334 
335 	/* Can't fit even one word. Break first word anywhere. */
336 	for (len = 1; len < maxlen; len++) {
337 		if (UTF8_CONTINUATION_BYTE(text[len]))
338 			continue;
339 		width = R_FontChunkLength(f, text, len);
340 		if (width > maxWidth)
341 			break;
342 		bestbreak = len;
343 		*widthp = width;
344 	}
345 
346 	return bestbreak;
347 }
348 
349 /**
350  * @brief Find longest part of text that fits in maxWidth pixels,
351  * with a marker (ellipsis) at the end to show that part of the text was
352  * truncated.
353  * Assumes whole string won't fit.
354  */
R_FontFindTruncFit(const font_t * f,const char * text,int maxlen,int maxWidth,bool mark,int * widthp)355 static int R_FontFindTruncFit (const font_t* f, const char* text, int maxlen, int maxWidth, bool mark, int* widthp)
356 {
357 	char buf[BUF_SIZE];
358 	int width;
359 	int len;
360 	int breaklen;
361 
362 	breaklen = 0;
363 	*widthp = 0;
364 
365 	for (len = 1; len < maxlen; len++) {
366 		buf[len - 1] = text[len - 1];
367 		if (UTF8_CONTINUATION_BYTE(text[len]))
368 			continue;
369 		if (mark)
370 			Q_strncpyz(&buf[len], truncmarker, sizeof(buf) - len);
371 		else
372 			buf[len] = '\0';
373 		TTF_SizeUTF8(f->font, buf, &width, nullptr);
374 		if (width > maxWidth)
375 			return breaklen;
376 		breaklen = len;
377 		*widthp = width;
378 	}
379 
380 	return maxlen;
381 }
382 
383 /**
384  * @brief Split text into chunks that fit on one line, and create cache
385  * entries for those chunks.
386  * @return number of chunks allocated in chunkCache.
387  */
R_FontMakeChunks(const font_t * f,const char * text,int maxWidth,longlines_t method,int * lines,bool * aborted)388 static int R_FontMakeChunks (const font_t* f, const char* text, int maxWidth, longlines_t method, int* lines, bool *aborted)
389 {
390 	int lineno = 0;
391 	int pos = 0;
392 	int startChunks = numChunks;
393 	char buf[BUF_SIZE];
394 
395 	assert(text);
396 
397 	Q_strncpyz(buf, text, sizeof(buf));
398 
399 	do {
400 		int width;
401 		int len;
402 		int utf8len;
403 		int skip = 0;
404 		bool truncated = false;
405 
406 		/* find mandatory break */
407 		len = strcspn(&buf[pos], "\n");
408 
409 		/* tidy up broken UTF-8 at end of line which may have been
410 		 * truncated by caller by use of functions like Q_strncpyz */
411 		utf8len = 1;
412 		while (len > utf8len && UTF8_CONTINUATION_BYTE(buf[pos + len - utf8len]))
413 			utf8len++;
414 		if (len > 0 && utf8len != UTF8_char_len(buf[pos + len - utf8len])) {
415 			len -= utf8len;
416 			skip += utf8len;
417 		}
418 
419 		/* delete trailing spaces */
420 		while (len > 0 && buf[pos + len - 1] == ' ') {
421 			len--;
422 			skip++;
423 		}
424 
425 		width = R_FontChunkLength(f, &buf[pos], len);
426 		if (maxWidth > 0 && width > maxWidth) {
427 			if (method == LONGLINES_WRAP) {
428 				/* full chunk didn't fit; try smaller */
429 				len = R_FontFindFit(f, &buf[pos], len, maxWidth, &width);
430 				/* skip following spaces */
431 				skip = 0;
432 				while (buf[pos + len + skip] == ' ')
433 					skip++;
434 				if (len + skip == 0) {
435 					*aborted = true;
436 					break; /* could not fit even one character */
437 				}
438 			} else {
439 				truncated = (method == LONGLINES_PRETTYCHOP);
440 				len = R_FontFindTruncFit(f, &buf[pos], len, maxWidth, truncated, &width);
441 				skip = strcspn(&buf[pos + len], "\n");
442 			}
443 		}
444 		if (width > 0) {
445 			/* add chunk to cache */
446 			if (numChunks >= MAX_CHUNK_CACHE) {
447 				/* whoops, ran out of cache, wipe cache and start over */
448 				R_FontCleanCache();
449 				/** @todo check for infinite recursion here */
450 				return R_FontMakeChunks(f, text, maxWidth, method, lines, aborted);
451 			}
452 			chunkCache[numChunks].pos = pos;
453 			chunkCache[numChunks].len = len;
454 			chunkCache[numChunks].linenum = lineno;
455 			chunkCache[numChunks].width = width;
456 			chunkCache[numChunks].truncated = truncated;
457 			numChunks++;
458 		}
459 		pos += len + skip;
460 		if (buf[pos] == '\n' || buf[pos] == '\\') {
461 			pos++;
462 		}
463 		lineno++;
464 	} while (buf[pos] != '\0');
465 
466 	/* If there were empty lines at the end of the text, then lineno might
467 	 * be greater than the linenum of the last chunk. Some callers need to
468 	 * know this to count lines accurately. */
469 	*lines = lineno;
470 	return numChunks - startChunks;
471 }
472 
473 /**
474  * @brief Wrap text according to provided parameters.
475  * Pull wrapping from cache if possible.
476  */
R_FontWrapText(const font_t * f,const char * text,int maxWidth,longlines_t method)477 static wrapCache_t* R_FontWrapText (const font_t* f, const char* text, int maxWidth, longlines_t method)
478 {
479 	wrapCache_t* wrap;
480 	int hashValue = R_FontHash(text ,f);
481 	int chunksUsed;
482 	int lines;
483 	bool aborted = false;
484 
485 	/* String is considered a match if the part that fit in entry->string
486 	 * matches. Since the hash value also matches and the hash was taken
487 	 * over the whole string, this is good enough. */
488 	for (wrap = hash[hashValue]; wrap; wrap = wrap->next)
489 		/* big string are cut, we must not test the 256th character ('\0') */
490 		if (!strncmp(text, wrap->text, sizeof(wrap->text) - 1)
491 		 && wrap->font == f
492 		 && wrap->method == method
493 		 && (wrap->maxWidth == maxWidth
494 			|| (wrap->numLines == 1 && !wrap->aborted
495 				&& !chunkCache[wrap->chunkIdx].truncated
496 				&& (chunkCache[wrap->chunkIdx].width <= maxWidth || maxWidth <= 0))))
497 			return wrap;
498 
499 	if (numWraps >= MAX_WRAP_CACHE)
500 		R_FontCleanCache();
501 
502 	/* It is possible that R_FontMakeChunks will wipe the cache,
503 	 * so do not rely on numWraps until it completes. */
504 	chunksUsed = R_FontMakeChunks(f, text, maxWidth, method, &lines, &aborted);
505 
506 	wrap = &wrapCache[numWraps];
507 	strncpy(wrap->text, text, sizeof(wrap->text));
508 	wrap->text[sizeof(wrap->text) - 1] = '\0';
509 	wrap->font = f;
510 	wrap->maxWidth = maxWidth;
511 	wrap->method = method;
512 	wrap->aborted = aborted;
513 	wrap->numChunks = chunksUsed;
514 	wrap->numLines = lines;
515 	wrap->chunkIdx = numChunks - chunksUsed;
516 
517 	/* insert new text into wrap cache */
518 	wrap->next = hash[hashValue];
519 	hash[hashValue] = wrap;
520 	numWraps++;
521 
522 	return wrap;
523 }
524 
525 /**
526  * @brief Supply information about the size of the text when it is
527  * linewrapped and rendered, without actually rendering it. Any of the
528  * output parameters may be nullptr.
529  * @param[in] fontId the font id (defined in ufos/fonts.ufo)
530  * @param[in] text The text to check
531  * @param maxWidth Max width available
532  * @param method Line overflow method
533  * @param[out] width receives width in pixels of the longest line in the text
534  * @param[out] height receives height in pixels when rendered with standard line height
535  * @param[out] lines receives total number of lines in text, including blank ones
536  * @param[out] isTruncated receives true, if the text must be trucated
537  */
R_FontTextSize(const char * fontId,const char * text,int maxWidth,longlines_t method,int * width,int * height,int * lines,bool * isTruncated)538 void R_FontTextSize (const char* fontId, const char* text, int maxWidth, longlines_t method, int* width, int* height, int* lines, bool *isTruncated)
539 {
540 	const font_t* font = R_GetFont(fontId);
541 	const wrapCache_t* wrap = R_FontWrapText(font, text, maxWidth, method);
542 
543 	if (width) {
544 		int i;
545 		*width = 0;
546 		for (i = 0; i < wrap->numChunks; i++) {
547 			if (chunkCache[wrap->chunkIdx + i].width > *width)
548 				*width = chunkCache[wrap->chunkIdx + i].width;
549 		}
550 	}
551 
552 	if (height)
553 		*height = (wrap->numLines - 1) * font->lineSkip + font->height;
554 
555 	if (lines)
556 		*lines = wrap->numLines;
557 
558 	if (isTruncated)
559 		*isTruncated = chunkCache[wrap->chunkIdx].truncated;
560 }
561 
562 /**
563  * @brief Renders the text surface and converts to 32bit SDL_Surface that is stored in font_t structure
564  * @sa R_FontCacheGLSurface
565  * @sa TTF_RenderUTF8_Blended
566  * @sa SDL_CreateRGBSurface
567  * @sa SDL_LowerBlit
568  * @sa SDL_FreeSurface
569  */
R_FontGenerateTexture(const font_t * font,const char * text,chunkCache_t * chunk)570 static void R_FontGenerateTexture (const font_t* font, const char* text, chunkCache_t* chunk)
571 {
572 	int w, h;
573 	SDL_Surface* textSurface;
574 	SDL_Surface* openGLSurface;
575 	SDL_Rect rect = {0, 0, 0, 0};
576 	char buf[BUF_SIZE];
577 	static const SDL_Color color = {255, 255, 255, 0};	/* The 4th value is unused */
578 	int colordepth = 32;
579 
580 #ifdef GL_VERSION_ES_CM_1_0
581 	const int samples = GL_RGBA;
582 	int pixelFormat = GL_RGBA; /* There's no GL_BGRA symbol defined in Android GLES headers */
583 #else
584 	const int samples = r_config.gl_compressed_alpha_format ? r_config.gl_compressed_alpha_format : r_config.gl_alpha_format;
585 	int pixelFormat = GL_BGRA;
586 #endif
587 
588 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
589 	Uint32 rmask = 0xff000000;
590 	Uint32 gmask = 0x00ff0000;
591 	Uint32 bmask = 0x0000ff00;
592 	Uint32 amask = 0x000000ff;
593 #else
594 	Uint32 rmask = 0x000000ff;
595 	Uint32 gmask = 0x0000ff00;
596 	Uint32 bmask = 0x00ff0000;
597 	Uint32 amask = 0xff000000;
598 #endif
599 
600 	if (chunk->texnum != 0)
601 		return;  /* already generated */
602 
603 	assert(strlen(text) >= chunk->pos + chunk->len);
604 	if (chunk->len >= sizeof(buf))
605 		return;
606 	memcpy(buf, &text[chunk->pos], chunk->len);
607 	buf[chunk->len] = 0;
608 
609 	if (chunk->truncated)
610 		Q_strncpyz(buf + chunk->len, truncmarker, sizeof(buf) - chunk->len);
611 
612 	textSurface = TTF_RenderUTF8_Blended(font->font, buf, color);
613 	if (!textSurface) {
614 		Com_Printf("%s (%s)\n", TTF_GetError(), buf);
615 		return;
616 	}
617 
618 	/* copy text to a surface of suitable size for a texture (power of two) */
619 	for (w = 2; w < textSurface->w; w <<= 1) {}
620 	for (h = 2; h < textSurface->h; h <<= 1) {}
621 
622 	openGLSurface = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, colordepth, rmask, gmask, bmask, amask);
623 	if (!openGLSurface)
624 		return;
625 
626 	rect.x = rect.y = 0;
627 	rect.w = textSurface->w;
628 	if (rect.w > chunk->width)
629 		rect.w = chunk->width;
630 	rect.h = textSurface->h;
631 
632 	/* ignore alpha when blitting - just copy it over */
633 #if SDL_VERSION_ATLEAST(2,0,0)
634 	SDL_SetSurfaceAlphaMod(textSurface, 255);
635 #else
636 	SDL_SetAlpha(textSurface, 0, 255);
637 #endif
638 
639 	SDL_LowerBlit(textSurface, &rect, openGLSurface, &rect);
640 	SDL_FreeSurface(textSurface);
641 
642 	glGenTextures(1, &chunk->texnum);
643 	R_BindTexture(chunk->texnum);
644 	glTexImage2D(GL_TEXTURE_2D, 0, samples, w, h, 0, pixelFormat, GL_UNSIGNED_BYTE, openGLSurface->pixels);
645 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
646 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
647 	Vector2Set(chunk->texsize, w, h);
648 	R_CheckError();
649 	SDL_FreeSurface(openGLSurface);
650 }
651 
652 static const float font_texcoords[] = {
653 	0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0
654 };
655 
R_FontDrawTexture(int texId,int x,int y,int w,int h)656 static void R_FontDrawTexture (int texId, int x, int y, int w, int h)
657 {
658 	const float nx = x * viddef.rx;
659 	const float ny = y * viddef.ry;
660 	const float nw = w * viddef.rx;
661 	const float nh = h * viddef.ry;
662 
663 	R_BindTexture(texId);
664 
665 	glVertexPointer(2, GL_SHORT, 0, r_state.vertex_array_2d);
666 
667 	memcpy(texunit_diffuse.texcoord_array, font_texcoords, sizeof(font_texcoords));
668 
669 	r_state.vertex_array_2d[0] = nx;
670 	r_state.vertex_array_2d[1] = ny;
671 	r_state.vertex_array_2d[2] = nx + nw;
672 	r_state.vertex_array_2d[3] = ny;
673 
674 	r_state.vertex_array_2d[4] = nx;
675 	r_state.vertex_array_2d[5] = ny + nh;
676 	r_state.vertex_array_2d[6] = nx + nw;
677 	r_state.vertex_array_2d[7] = ny + nh;
678 
679 	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
680 
681 	refdef.batchCount++;
682 
683 	/* set back to standard 3d pointer */
684 	glVertexPointer(3, GL_FLOAT, 0, r_state.vertex_array_3d);
685 }
686 
687 /**
688  * @param[in] fontId the font id (defined in ufos/fonts.ufo)
689  * @param align Alignment of the text inside the text zone
690  * @param[in] x Current x position (may differ from absX due to tabs e.g.)
691  * @param[in] y Current y position (may differ from absY due to linebreaks)
692  * @param[in] absX Absolute x value for this string
693  * @param[in] maxWidth Max width - relative from absX
694  * @param[in] lineHeight The lineheight of that node
695  * @param[in] c The string to draw
696  * @param boxHeight Number of line the box can contain. If <= 0 the value is
697  * autogenerated according to the number of line of the text input
698  * @param[in] scrollPos Starting line in this node (due to scrolling)
699  * @param[in] curLine Current line (see lineHeight)
700  * @param method Explain the way we manage line overflow
701  * @note the x, y, width and height values are all normalized here - don't use the
702  * viddef settings for drawstring calls - make them all relative to VID_NORM_WIDTH
703  * and VID_NORM_HEIGHT
704  * @todo This could be replaced with a set of much simpler interfaces.
705  */
R_FontDrawString(const char * fontId,align_t align,int x,int y,int absX,int maxWidth,int lineHeight,const char * c,int boxHeight,int scrollPos,int * curLine,longlines_t method)706 int R_FontDrawString (const char* fontId, align_t align, int x, int y, int absX, int maxWidth,
707 		int lineHeight, const char* c, int boxHeight, int scrollPos, int* curLine, longlines_t method)
708 {
709 	const font_t* font = R_GetFont(fontId);
710 	const wrapCache_t* wrap;
711 	int i;
712 	const align_t horizontalAlign = (align_t)(align % 3); /* left, center, right */
713 	int xalign = 0;
714 
715 	wrap = R_FontWrapText(font, c, maxWidth - (x - absX), method);
716 
717 	if (boxHeight <= 0)
718 		boxHeight = wrap->numLines;
719 
720 	for (i = 0; i < wrap->numChunks; i++) {
721 		chunkCache_t* chunk = &chunkCache[wrap->chunkIdx + i];
722 		int linenum = chunk->linenum;
723 
724 		if (curLine)
725 			linenum += *curLine;
726 
727 		if (horizontalAlign == 1)
728 			xalign = -(chunk->width / 2);
729 		else if (horizontalAlign == 2)
730 			xalign = -chunk->width;
731 		else
732 			xalign = 0;
733 
734 		if (linenum < scrollPos || linenum >= scrollPos + boxHeight)
735 			continue;
736 
737 		R_FontGenerateTexture(font, c, chunk);
738 		R_FontDrawTexture(chunk->texnum, x + xalign, y + (linenum - scrollPos) * lineHeight, chunk->texsize[0], chunk->texsize[1]);
739 	}
740 
741 	return wrap->numLines;
742 }
743 
R_FontInit(void)744 void R_FontInit (void)
745 {
746 #ifdef SDL_TTF_VERSION
747 	SDL_version version;
748 
749 	SDL_TTF_VERSION(&version)
750 	Com_Printf("SDL_ttf version %i.%i.%i - we need at least 2.0.7\n",
751 		version.major,
752 		version.minor,
753 		version.patch);
754 #else
755 	Com_Printf("could not get SDL_ttf version - we need at least 2.0.7\n");
756 #endif
757 
758 	numFonts = 0;
759 	OBJZERO(fonts);
760 
761 	OBJZERO(chunkCache);
762 	OBJZERO(wrapCache);
763 	OBJZERO(hash);
764 	numChunks = 0;
765 	numWraps = 0;
766 
767 	/* init the truetype font engine */
768 	if (TTF_Init() == -1)
769 		Com_Error(ERR_FATAL, "SDL_ttf error: %s", TTF_GetError());
770 }
771 
R_FontRegister(const char * name,int size,const char * path,const char * style)772 void R_FontRegister (const char* name, int size, const char* path, const char* style)
773 {
774 	int renderstyle = TTF_STYLE_NORMAL;		/* NORMAL is standard */
775 
776 	if (style && style[0] != '\0') {
777 		int i;
778 		for (i = 0; i < NUM_FONT_STYLES; i++)
779 			if (!Q_strcasecmp(fontStyle[i].name, style)) {
780 				renderstyle = fontStyle[i].renderStyle;
781 				break;
782 			}
783 	}
784 
785 	R_FontAnalyze(name, path, renderstyle, size);
786 }
787