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