1 /*
2 Copyright (C) 1997-2001 Id Software, Inc.
3 
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8 
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12 
13 See the GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 */
19 
20 //
21 // rf_font.c
22 // FIXME TODO:
23 // - Hash optimize (though the table size should be pretty small, like 4).
24 // - Different materials for different size ranges (like smallMaterial fonts/quake3_sm.tga, mediumMaterial, largeMaterial, etc).
25 // - Per-character coords and width/height?
26 // - More font styles, italic, bold, etc...
27 // - shaderTime passed to font renders?
28 //	FS_ALIGN_CENTER			= 1 << 0,
29 //	FS_ALIGN_RIGHT			= 1 << 1,
30 //	FS_ITALIC				= 1 << 2,
31 //
32 
33 #include "rf_local.h"
34 
35 #define MAX_FONTS		64
36 #define MAX_FONT_SIZES	8
37 
38 typedef struct fontSize_s {
39 	float		minScale;
40 	char		matName[MAX_QPATH];
41 	shader_t	*matPtr;
42 } fontSize_t;
43 
44 typedef struct font_s {
45 	char			name[MAX_QPATH-11];	// fonts/[name].font
46 	uint32			touchFrame;
47 
48 	byte			numSizes;
49 	fontSize_t		fontSizes[MAX_FONT_SIZES];
50 
51 	float			charWidth;
52 	float			charHeight;
53 } font_t;
54 
55 static font_t		r_curFont;
56 
57 static font_t		r_fontList[MAX_FONTS];
58 static uint32		r_numFonts;
59 
60 static vec4_t		r_fontShadowClr;	// Only the alpha value ever changes
61 
62 /*
63 =============================================================================
64 
65 	FONT REGISTRATION
66 
67 =============================================================================
68 */
69 
70 /*
71 ===============
72 R_FindFont
73 ===============
74 */
R_FindFont(char * name)75 static inline font_t *R_FindFont (char *name)
76 {
77 	font_t	*font;
78 	uint32	i;
79 
80 	assert (name && name[0]);
81 	ri.reg.fontsSeaked++;
82 
83 	for (i=0, font=&r_fontList[0] ; i<r_numFonts ; font++, i++) {
84 		if (!font->touchFrame)
85 			continue;	// Free slot
86 
87 		// Check name
88 		if (!strcmp (font->name, name))
89 			return font;
90 	}
91 
92 	return NULL;
93 }
94 
95 
96 /*
97 ===============
98 R_TouchFont
99 ===============
100 */
R_TouchFont(font_t * font)101 static inline void R_TouchFont (font_t *font)
102 {
103 	fontSize_t	*fs;
104 	byte		i;
105 
106 	assert (font);
107 
108 	// Register the material(s)
109 	for (i=0, fs=font->fontSizes ; i<font->numSizes ; fs++, i++) {
110 		fs->matPtr = R_RegisterPic (fs->matName);
111 		if (!fs->matPtr)
112 			Com_Printf (PRNT_ERROR, "R_TouchFont: could not locate material '%s'!\n", fs->matName);
113 	}
114 
115 	// Used this sequence
116 	font->touchFrame = ri.reg.registerFrame;
117 	ri.reg.fontsTouched++;
118 }
119 
120 
121 /*
122 ===============
123 R_FreeFont
124 ===============
125 */
R_FreeFont(font_t * font)126 static inline void R_FreeFont (font_t *font)
127 {
128 	assert (font);
129 	if (!font)
130 		return;
131 
132 	// Free it
133 	memset (font, 0, sizeof (font_t));
134 	ri.reg.fontsReleased++;
135 }
136 
137 
138 /*
139 ===============
140 R_RegisterFont
141 ===============
142 */
R_RegisterFont(char * name)143 font_t *R_RegisterFont (char *name)
144 {
145 	char		fixedName[MAX_QPATH];
146 	char		*buf;
147 	int			fileLen;
148 	char		*token;
149 	float		floatTok;
150 	parse_t		*ps;
151 	font_t		*font;
152 	uint32		i;
153 
154 	// Check the name
155 	if (!name || !name[0]) {
156 		Com_Printf (PRNT_ERROR, "R_RegisterFont: NULL name!\n");
157 		return NULL;
158 	}
159 	if (strchr (name, '/') || strchr (name, '\\') || strchr (name, '.')) {
160 		Com_Printf (PRNT_ERROR, "R_RegisterFont: name can not be a path ('%s')!\n", name);
161 		return NULL;
162 	}
163 	Q_strncpyz (fixedName, name, sizeof (fixedName));
164 	Q_strlwr (fixedName);
165 
166 	// See if it's already loaded
167 	font = R_FindFont (name);
168 	if (font) {
169 		R_TouchFont (font);
170 		return font;
171 	}
172 
173 	// Nope, use the scratch font spot for writing
174 	font = &r_curFont;
175 	memset (&r_curFont, 0, sizeof (font_t));
176 	Q_strncpyz (font->name, name, sizeof (font->name));
177 	Q_strlwr (font->name);
178 	font->charWidth = 8.0f;
179 	font->charHeight = 8.0f;
180 
181 	// Load the file
182 	Q_snprintfz (fixedName, sizeof (fixedName), "fonts/%s.font", name);
183 	fileLen = FS_LoadFile (fixedName, (void **)&buf, "\n\0");
184 	if (!buf || fileLen <= 0) {
185 		Com_Printf (PRNT_ERROR, "ERROR: can't load '%s' -- %s\n", name, (fileLen == -1) ? "not found" : "empty file");
186 		return NULL;
187 	}
188 
189 	// Parse the file
190 	ps = PS_StartSession (buf, PSP_COMMENT_BLOCK|PSP_COMMENT_LINE|PSP_COMMENT_POUND);
191 	for ( ; PS_ParseToken (ps, PSF_ALLOW_NEWLINES|PSF_TO_LOWER, &token) ; ) {
192 		// Parse the materials
193 		if (!strcmp (token, "charwidth")) {
194 			// Width
195 			if (!PS_ParseDataType (ps, 0, PSDT_FLOAT, &floatTok, 1)) {
196 				Com_Printf (PRNT_ERROR, "ERROR: invalid/missing parameters for '%s'\n", token);
197 				break;
198 			}
199 			if (floatTok < 0) {
200 				Com_Printf (PRNT_ERROR, "ERROR: invalid/missing parameters for '%s'\n", token);
201 				break;
202 			}
203 
204 			font->charWidth = floatTok;
205 			continue;
206 		}
207 		else if (!strcmp (token, "charheight")) {
208 			// Height
209 			if (!PS_ParseDataType (ps, 0, PSDT_FLOAT, &floatTok, 1)) {
210 				Com_Printf (PRNT_ERROR, "ERROR: invalid/missing parameters for '%s'\n", token);
211 				break;
212 			}
213 			if (floatTok < 0) {
214 				Com_Printf (PRNT_ERROR, "ERROR: invalid/missing parameters for '%s'\n", token);
215 				break;
216 			}
217 
218 			font->charHeight = floatTok;
219 			continue;
220 		}
221 		else if (!strcmp (token, "mat")) {
222 			// Check for too many
223 			if (font->numSizes+1 >= MAX_FONT_SIZES) {
224 				Com_Printf (PRNT_ERROR, "ERROR: too many size definitions!\n");
225 				break;
226 			}
227 
228 			// Scale
229 			if (!PS_ParseDataType (ps, 0, PSDT_FLOAT, &floatTok, 1)) {
230 				Com_Printf (PRNT_ERROR, "ERROR: invalid/missing parameters for '%s'\n", token);
231 				break;
232 			}
233 			if (floatTok < 0) {
234 				Com_Printf (PRNT_ERROR, "ERROR: invalid/missing parameters for '%s'\n", token);
235 				break;
236 			}
237 
238 			// Name
239 			if (!PS_ParseToken (ps, PSF_TO_LOWER, &token)) {
240 				Com_Printf (PRNT_ERROR, "ERROR: invalid/missing parameters for '%s'\n", "mat");
241 				break;
242 			}
243 
244 			Q_strncpyz (font->fontSizes[font->numSizes].matName, token, sizeof (font->fontSizes[font->numSizes].matName));
245 			font->fontSizes[font->numSizes].minScale = floatTok;
246 			font->numSizes++;
247 			continue;
248 		}
249 
250 		Com_Printf (PRNT_ERROR, "ERROR: unknown key '%s'\n", token);
251 	}
252 
253 	// Done parsing
254 	PS_EndSession (ps);
255 	FS_FreeFile (buf);
256 
257 	// Make sure there's at least one sizedef
258 	if (font->numSizes == 0) {
259 		Com_Printf (PRNT_ERROR, "ERROR: no valid fonts for '%s'!\n", name);
260 		return NULL;
261 	}
262 
263 	// Find a free slot
264 	for (i=0, font=&r_fontList[0] ; i<r_numFonts ; font++, i++) {
265 		if (font->touchFrame)
266 			continue;	// In use
267 		break;
268 	}
269 
270 	// None found, make a new slot
271 	if (i == r_numFonts) {
272 		if (r_numFonts+1 >= MAX_FONTS)
273 			Com_Error (ERR_DROP, "R_RegisterFont: MAX_FONTS");
274 
275 		font = &r_fontList[r_numFonts++];
276 	}
277 
278 	// Copy from scratch space, touch, finish
279 	memcpy (font, &r_curFont, sizeof (font_t));
280 	R_TouchFont (font);
281 	return font;
282 }
283 
284 
285 /*
286 ===============
287 R_EndFontRegistration
288 ===============
289 */
R_EndFontRegistration(void)290 void R_EndFontRegistration (void)
291 {
292 	font_t		*font;
293 	uint32		i;
294 
295 	// Free un-touched fonts
296 	for (i=0, font=&r_fontList[0] ; i<r_numFonts ; font++, i++) {
297 		if (!font->touchFrame)
298 			continue;	// Free spot
299 		if (font->touchFrame == ri.reg.registerFrame)
300 			continue;	// Used in this sequence
301 
302 		R_FreeFont (font);
303 	}
304 }
305 
306 
307 /*
308 ===============
309 R_CheckFont
310 ===============
311 */
R_CheckFont(void)312 void R_CheckFont (void)
313 {
314 	// Load console characters
315 	r_defaultFont->modified = qFalse;
316 
317 	// Load the font
318 	ri.media.defaultFont = R_RegisterFont (r_defaultFont->string);
319 	if (!ri.media.defaultFont && strcmp (r_defaultFont->string, "default"))
320 		ri.media.defaultFont = R_RegisterFont ("default");
321 
322 	if (!ri.media.defaultFont)
323 		Com_Error (ERR_FATAL, "R_CheckFont: unable to load default font!");
324 }
325 
326 /*
327 =============================================================================
328 
329 	FONT RENDERING
330 
331 =============================================================================
332 */
333 
334 /*
335 ================
336 R_ShaderForFont
337 ================
338 */
339 // FIXME: let format decide based on x/y scale
R_ShaderForFont(font_t * font,float xScale,float yScale)340 static shader_t *R_ShaderForFont (font_t *font, float xScale, float yScale)
341 {
342 	fontSize_t	*fs;
343 	byte		i;
344 	shader_t	*bestMat;
345 	float		bestScale;
346 
347 	assert (font);
348 
349 	// Find the most appropriate scale
350 	bestMat = NULL;
351 	bestScale = -1;
352 	for (i=0, fs=font->fontSizes ; i<font->numSizes ; fs++, i++) {
353 		if (xScale >= fs->minScale && xScale > bestScale) {
354 			bestScale = fs->minScale;
355 			bestMat = fs->matPtr;
356 		}
357 	}
358 
359 	return bestMat;
360 }
361 
362 
363 /*
364 ================
365 R_GetFontDimensions
366 ================
367 */
368 // FIXME: (font_t *font, vec2_t scale, uint32 flags, vec2_t target)
369 // FIXME: adjust for shadowing flag?
R_GetFontDimensions(font_t * font,float xScale,float yScale,uint32 flags,vec2_t dest)370 void R_GetFontDimensions (font_t *font, float xScale, float yScale, uint32 flags, vec2_t dest)
371 {
372 	if (!font)
373 		font = ri.media.defaultFont;
374 	if (!xScale && !yScale) {
375 		xScale = r_fontScale->floatVal;
376 		yScale = r_fontScale->floatVal;
377 	}
378 
379 	if (dest) {
380 		if (flags & FS_SQUARE) {
381 			if (font->charWidth >= font->charHeight) {
382 				dest[0] = font->charWidth * xScale;
383 				dest[1] = font->charWidth * yScale;
384 			}
385 			else {
386 				dest[0] = font->charHeight * xScale;
387 				dest[1] = font->charHeight * yScale;
388 			}
389 		}
390 		else {
391 			dest[0] = font->charWidth * xScale;
392 			dest[1] = font->charHeight * yScale;
393 		}
394 	}
395 }
396 
397 
398 /*
399 ================
400 R_DrawString
401 ================
402 */
R_DrawString(font_t * font,float x,float y,float xScale,float yScale,uint32 flags,char * string,vec4_t color)403 int R_DrawString (font_t *font, float x, float y, float xScale, float yScale, uint32 flags, char *string, vec4_t color)
404 {
405 	int			num, i;
406 	float		frow, fcol;
407 	float		startX;
408 	vec4_t		strColor;
409 	qBool		isShadowed;
410 	qBool		skipNext = qFalse;
411 	qBool		inColorCode = qFalse;
412 	shader_t	*shader;
413 	vec2_t		ftSize;
414 
415 	if (!string)
416 		return 0;
417 
418 	// Find the font
419 	if (!xScale && !yScale) {
420 		xScale = r_fontScale->floatVal;
421 		yScale = r_fontScale->floatVal;
422 	}
423 	if (!font)
424 		font = ri.media.defaultFont;
425 	if (!color)
426 		color = Q_colorWhite;
427 	shader = R_ShaderForFont (font, xScale, yScale);
428 
429 	Vec4Copy (color, strColor);
430 
431 	isShadowed = (flags & FS_SHADOW);
432 	Vec3Copy (Q_colorBlack, r_fontShadowClr);
433 	r_fontShadowClr[3] = strColor[3];
434 
435 	R_GetFontDimensions (font, xScale, yScale, flags, ftSize);
436 
437 	startX = x;
438 	for (i=0 ; *string ; ) {
439 		num = *string;
440 		if (flags & FS_SECONDARY && num < 128)
441 			num |= 128;
442 
443 		if (skipNext) {
444 			skipNext = qFalse;
445 		}
446 		else if ((num & 127) == COLOR_ESCAPE && *(string+1)) {
447 			switch (string[1] & 127) {
448 			case COLOR_ESCAPE:
449 				string++;
450 				skipNext = qTrue;
451 				continue;
452 
453 			case 'i':	case 'I':
454 				// FIXME: todo!
455 				string += 2;
456 				continue;
457 
458 			case 'r':	case 'R':
459 				isShadowed = (flags & FS_SHADOW);
460 				inColorCode = qFalse;
461 				Vec3Copy (Q_colorWhite, strColor);
462 				string += 2;
463 				continue;
464 
465 			case 's':	case 'S':
466 				isShadowed = !isShadowed || (flags & FS_SHADOW);
467 				string += 2;
468 				continue;
469 
470 			case COLOR_BLACK:
471 			case COLOR_RED:
472 			case COLOR_GREEN:
473 			case COLOR_YELLOW:
474 			case COLOR_BLUE:
475 			case COLOR_CYAN:
476 			case COLOR_MAGENTA:
477 			case COLOR_WHITE:
478 			case COLOR_GREY:
479 				Vec3Copy (Q_strColorTable[Q_StrColorIndex (string[1])], strColor);
480 				inColorCode = qTrue;
481 				string += 2;
482 				continue;
483 			}
484 		}
485 		else if ((num & 127) == '\n') {
486 			x = startX;
487 			y += ftSize[1];
488 			string++;
489 			continue;
490 		}
491 
492 		if (inColorCode)
493 			num &= 127;
494 		else
495 			num &= 255;
496 
497 		// Skip spaces
498 		if ((num&127) != 32) {
499 			frow = (num>>4) * (1.0f/16.0f);
500 			fcol = (num&15) * (1.0f/16.0f);
501 
502 			if (isShadowed)
503 				R_DrawPic (shader, 0, x+2, y+2, ftSize[0], ftSize[1], fcol, frow, fcol+(1.0f/16.0f), frow+(1.0f/16.0f), r_fontShadowClr);
504 
505 			R_DrawPic (shader, 0, x, y, ftSize[0], ftSize[1], fcol, frow, fcol+(1.0f/16.0f), frow+(1.0f/16.0f), strColor);
506 		}
507 
508 		x += ftSize[0];
509 		string++;
510 		i++;
511 	}
512 
513 	return i;
514 }
515 
516 
517 /*
518 ================
519 R_DrawStringLen
520 ================
521 */
R_DrawStringLen(font_t * font,float x,float y,float xScale,float yScale,uint32 flags,char * string,int len,vec4_t color)522 int R_DrawStringLen (font_t *font, float x, float y, float xScale, float yScale, uint32 flags, char *string, int len, vec4_t color)
523 {
524 	char	swap;
525 	int		length;
526 
527 	if (len < 0)
528 		return R_DrawString (font, x, y, xScale, yScale, flags, string, color);
529 
530 	swap = string[len];
531 	string[len] = 0;
532 	length = R_DrawString (font, x, y, xScale, yScale, flags, string, color);
533 	string[len] = swap;
534 
535 	return length;
536 }
537 
538 
539 /*
540 ================
541 R_DrawChar
542 ================
543 */
R_DrawChar(font_t * font,float x,float y,float xScale,float yScale,uint32 flags,int num,vec4_t color)544 void R_DrawChar (font_t *font, float x, float y, float xScale, float yScale, uint32 flags, int num, vec4_t color)
545 {
546 	float		frow, fcol;
547 	shader_t	*shader;
548 	vec2_t		ftSize;
549 
550 	if (!xScale && !yScale) {
551 		xScale = r_fontScale->floatVal;
552 		yScale = r_fontScale->floatVal;
553 	}
554 	if (!font)
555 		font = ri.media.defaultFont;
556 	if (!color)
557 		color = Q_colorWhite;
558 	shader = R_ShaderForFont (font, xScale, yScale);
559 
560 	R_GetFontDimensions (font, xScale, yScale, flags, ftSize);
561 
562 	if (flags & FS_SECONDARY && num < 128)
563 		num |= 128;
564 	num &= 255;
565 
566 	if ((num&127) == 32)
567 		return;	// Space
568 
569 	frow = (num>>4) * (1.0f/16.0f);
570 	fcol = (num&15) * (1.0f/16.0f);
571 
572 	if (flags & FS_SHADOW) {
573 		r_fontShadowClr[3] = color[3];
574 		R_DrawPic (shader, 0, x+2, y+2, ftSize[0], ftSize[1], fcol, frow, fcol+(1.0f/16.0f), frow+(1.0f/16.0f), r_fontShadowClr);
575 	}
576 
577 	R_DrawPic (shader, 0, x, y, ftSize[0], ftSize[1], fcol, frow, fcol+(1.0f/16.0f), frow+(1.0f/16.0f), color);
578 }
579 
580 /*
581 =============================================================================
582 
583 	INITIALIZATION
584 
585 =============================================================================
586 */
587 
588 /*
589 ================
590 R_FontInit
591 ================
592 */
R_FontInit(void)593 void R_FontInit (void)
594 {
595 	// Set values
596 	Vec3Copy (Q_colorBlack, r_fontShadowClr);
597 
598 	// FIXME: load all font files into memory here, and only register the images when needed?
599 
600 	Mem_CheckPoolIntegrity (ri.fontSysPool);
601 }
602 
603 
604 /*
605 ================
606 R_FontShutdown
607 ================
608 */
R_FontShutdown(void)609 void R_FontShutdown (void)
610 {
611 	font_t	*font;
612 	uint32	size, i;
613 
614 	Com_Printf (0, "Font system shutdown:\n");
615 	// Release fonts
616 	for (i=0, font=&r_fontList[0] ; i<r_numFonts ; font++, i++)
617 		R_FreeFont (font);
618 
619 	r_numFonts = 0;
620 	memset (&r_fontList[0], 0, sizeof (font_t) * MAX_FONTS);
621 
622 	// Empty the memory pool
623 	size = Mem_FreePool (ri.fontSysPool);
624 	Com_Printf (0, "...releasing %u bytes...\n", size);
625 }
626