1 /*
2 ===========================================================================
3
4 Return to Castle Wolfenstein multiplayer GPL Source Code
5 Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.
6
7 This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (RTCW MP Source Code).
8
9 RTCW MP Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 RTCW MP Source Code 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. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with RTCW MP Source Code. If not, see <http://www.gnu.org/licenses/>.
21
22 In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below.
23
24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
25
26 ===========================================================================
27 */
28
29 // tr_font.c
30 //
31 //
32 // The font system uses FreeType 2.x to render TrueType fonts for use within the game.
33 // As of this writing ( Nov, 2000 ) Team Arena uses these fonts for all of the ui and
34 // about 90% of the cgame presentation. A few areas of the CGAME were left uses the old
35 // fonts since the code is shared with standard Q3A.
36 //
37 // If you include this font rendering code in a commercial product you MUST include the
38 // following somewhere with your product, see www.freetype.org for specifics or changes.
39 // The Freetype code also uses some hinting techniques that MIGHT infringe on patents
40 // held by apple so be aware of that also.
41 //
42 // As of Q3A 1.25+ and Team Arena, we are shipping the game with the font rendering code
43 // disabled. This removes any potential patent issues and it keeps us from having to
44 // distribute an actual TrueTrype font which is 1. expensive to do and 2. seems to require
45 // an act of god to accomplish.
46 //
47 // What we did was pre-render the fonts using FreeType ( which is why we leave the FreeType
48 // credit in the credits ) and then saved off the glyph data and then hand touched up the
49 // font bitmaps so they scale a bit better in GL.
50 //
51 // There are limitations in the way fonts are saved and reloaded in that it is based on
52 // point size and not name. So if you pre-render Helvetica in 18 point and Impact in 18 point
53 // you will end up with a single 18 point data file and image set. Typically you will want to
54 // choose 3 sizes to best approximate the scaling you will be doing in the ui scripting system
55 //
56 // In the UI Scripting code, a scale of 1.0 is equal to a 48 point font. In Team Arena, we
57 // use three or four scales, most of them exactly equaling the specific rendered size. We
58 // rendered three sizes in Team Arena, 12, 16, and 20.
59 //
60 // To generate new font data you need to go through the following steps.
61 // 1. delete the fontImage_x_xx.tga files and fontImage_xx.dat files from the fonts path.
62 // 2. in a ui script, specificy a font, smallFont, and bigFont keyword with font name and
63 // point size. the original TrueType fonts must exist in fonts at this point.
64 // 3. run the game, you should see things normally.
65 // 4. Exit the game and there will be three dat files and at least three tga files. The
66 // tga's are in 256x256 pages so if it takes three images to render a 24 point font you
67 // will end up with fontImage_0_24.tga through fontImage_2_24.tga
68 // 5. In future runs of the game, the system looks for these images and data files when a
69 // specific point sized font is rendered and loads them for use.
70 // 6. Because of the original beta nature of the FreeType code you will probably want to hand
71 // touch the font bitmaps.
72 //
73 // Currently a define in the project turns on or off the FreeType code which is currently
74 // defined out. To pre-render new fonts you need enable the define ( BUILD_FREETYPE ) and
75 // uncheck the exclude from build check box in the FreeType2 area of the Renderer project.
76
77
78
79 #include "tr_local.h"
80 #include "../qcommon/qcommon.h"
81
82 #ifdef BUILD_FREETYPE
83 #ifdef USE_LOCAL_HEADERS
84 #include "../freetype-2.9/include/ft2build.h"
85 #else
86 #include <ft2build.h>
87 #endif
88 #include FT_FREETYPE_H
89 #include FT_ERRORS_H
90 #include FT_SYSTEM_H
91 #include FT_IMAGE_H
92 #include FT_OUTLINE_H
93
94 #define _FLOOR( x ) ( ( x ) & - 64 )
95 #define _CEIL( x ) ( ( ( x ) + 63 ) & - 64 )
96 #define _TRUNC( x ) ( ( x ) >> 6 )
97
98 FT_Library ftLibrary = NULL;
99 #endif
100
101 #define MAX_FONTS 6
102 static int registeredFontCount = 0;
103 static fontInfo_t registeredFont[MAX_FONTS];
104
105 #ifdef BUILD_FREETYPE
R_GetGlyphInfo(FT_GlyphSlot glyph,int * left,int * right,int * width,int * top,int * bottom,int * height,int * pitch)106 void R_GetGlyphInfo( FT_GlyphSlot glyph, int *left, int *right, int *width, int *top, int *bottom, int *height, int *pitch ) {
107 *left = _FLOOR( glyph->metrics.horiBearingX );
108 *right = _CEIL( glyph->metrics.horiBearingX + glyph->metrics.width );
109 *width = _TRUNC( *right - *left );
110
111 *top = _CEIL( glyph->metrics.horiBearingY );
112 *bottom = _FLOOR( glyph->metrics.horiBearingY - glyph->metrics.height );
113 *height = _TRUNC( *top - *bottom );
114 *pitch = ( qtrue ? ( *width + 3 ) & - 4 : ( *width + 7 ) >> 3 );
115 }
116
117
R_RenderGlyph(FT_GlyphSlot glyph,glyphInfo_t * glyphOut)118 FT_Bitmap *R_RenderGlyph( FT_GlyphSlot glyph, glyphInfo_t* glyphOut ) {
119 FT_Bitmap *bit2;
120 int left, right, width, top, bottom, height, pitch, size;
121
122 R_GetGlyphInfo( glyph, &left, &right, &width, &top, &bottom, &height, &pitch );
123
124 if ( glyph->format == ft_glyph_format_outline ) {
125 size = pitch * height;
126
127 bit2 = ri.Z_Malloc( sizeof( FT_Bitmap ) );
128
129 bit2->width = width;
130 bit2->rows = height;
131 bit2->pitch = pitch;
132 bit2->pixel_mode = ft_pixel_mode_grays;
133 //bit2->pixel_mode = ft_pixel_mode_mono;
134 bit2->buffer = ri.Z_Malloc( pitch * height );
135 bit2->num_grays = 256;
136
137 Com_Memset( bit2->buffer, 0, size );
138
139 FT_Outline_Translate( &glyph->outline, -left, -bottom );
140
141 FT_Outline_Get_Bitmap( ftLibrary, &glyph->outline, bit2 );
142
143 glyphOut->height = height;
144 glyphOut->pitch = pitch;
145 glyphOut->top = ( glyph->metrics.horiBearingY >> 6 ) + 1;
146 glyphOut->bottom = bottom;
147
148 return bit2;
149 } else {
150 ri.Printf( PRINT_ALL, "Non-outline fonts are not supported\n" );
151 }
152 return NULL;
153 }
154
WriteTGA(char * filename,byte * data,int width,int height)155 void WriteTGA (char *filename, byte *data, int width, int height) {
156 byte *buffer;
157 int i, c;
158 int row;
159 unsigned char *flip;
160 unsigned char *src, *dst;
161
162 buffer = ri.Z_Malloc(width*height*4 + 18);
163 Com_Memset (buffer, 0, 18);
164 buffer[2] = 2; // uncompressed type
165 buffer[12] = width&255;
166 buffer[13] = width>>8;
167 buffer[14] = height&255;
168 buffer[15] = height>>8;
169 buffer[16] = 32; // pixel size
170
171 // swap rgb to bgr
172 c = 18 + width * height * 4;
173 for ( i = 18 ; i < c ; i += 4 )
174 {
175 buffer[i] = data[i - 18 + 2]; // blue
176 buffer[i + 1] = data[i - 18 + 1]; // green
177 buffer[i + 2] = data[i - 18 + 0]; // red
178 buffer[i + 3] = data[i - 18 + 3]; // alpha
179 }
180
181 // flip upside down
182 flip = (unsigned char *)ri.Z_Malloc(width*4);
183 for(row = 0; row < height/2; row++)
184 {
185 src = buffer + 18 + row * 4 * width;
186 dst = buffer + 18 + (height - row - 1) * 4 * width;
187
188 Com_Memcpy(flip, src, width*4);
189 Com_Memcpy(src, dst, width*4);
190 Com_Memcpy(dst, flip, width*4);
191 }
192 ri.Free(flip);
193
194 ri.FS_WriteFile(filename, buffer, c);
195
196 //f = fopen (filename, "wb");
197 //fwrite (buffer, 1, c, f);
198 //fclose (f);
199
200 ri.Free( buffer );
201 }
202
RE_ConstructGlyphInfo(unsigned char * imageOut,int * xOut,int * yOut,int * maxHeight,FT_Face face,const unsigned char c,qboolean calcHeight)203 static glyphInfo_t *RE_ConstructGlyphInfo( unsigned char *imageOut, int *xOut, int *yOut, int *maxHeight, FT_Face face, const unsigned char c, qboolean calcHeight ) {
204 int i;
205 static glyphInfo_t glyph;
206 unsigned char *src, *dst;
207 float scaled_width, scaled_height;
208 FT_Bitmap *bitmap = NULL;
209
210 Com_Memset( &glyph, 0, sizeof( glyphInfo_t ) );
211 // make sure everything is here
212 if ( face != NULL ) {
213 FT_Load_Glyph( face, FT_Get_Char_Index( face, c ), FT_LOAD_DEFAULT );
214 bitmap = R_RenderGlyph( face->glyph, &glyph );
215 if ( bitmap ) {
216 glyph.xSkip = ( face->glyph->metrics.horiAdvance >> 6 ) + 1;
217 } else {
218 return &glyph;
219 }
220
221 if ( glyph.height > *maxHeight ) {
222 *maxHeight = glyph.height;
223 }
224
225 if ( calcHeight ) {
226 ri.Free( bitmap->buffer );
227 ri.Free( bitmap );
228 return &glyph;
229 }
230
231 /*
232 // need to convert to power of 2 sizes so we do not get
233 // any scaling from the gl upload
234 for (scaled_width = 1 ; scaled_width < glyph.pitch ; scaled_width<<=1)
235 ;
236 for (scaled_height = 1 ; scaled_height < glyph.height ; scaled_height<<=1)
237 ;
238 */
239
240 scaled_width = glyph.pitch;
241 scaled_height = glyph.height;
242
243 // we need to make sure we fit
244 if (*xOut + scaled_width + 1 >= 255) {
245 *xOut = 0;
246 *yOut += *maxHeight + 1;
247 }
248
249 if (*yOut + *maxHeight + 1 >= 255) {
250 *yOut = -1;
251 *xOut = -1;
252 ri.Free( bitmap->buffer );
253 ri.Free( bitmap );
254 return &glyph;
255 }
256
257 src = bitmap->buffer;
258 dst = imageOut + ( *yOut * 256 ) + *xOut;
259
260 if ( bitmap->pixel_mode == ft_pixel_mode_mono ) {
261 for ( i = 0; i < glyph.height; i++ ) {
262 int j;
263 unsigned char *_src = src;
264 unsigned char *_dst = dst;
265 unsigned char mask = 0x80;
266 unsigned char val = *_src;
267 for ( j = 0; j < glyph.pitch; j++ ) {
268 if ( mask == 0x80 ) {
269 val = *_src++;
270 }
271 if ( val & mask ) {
272 *_dst = 0xff;
273 }
274 mask >>= 1;
275
276 if ( mask == 0 ) {
277 mask = 0x80;
278 }
279 _dst++;
280 }
281
282 src += glyph.pitch;
283 dst += 256;
284 }
285 } else {
286 for ( i = 0; i < glyph.height; i++ ) {
287 Com_Memcpy( dst, src, glyph.pitch );
288 src += glyph.pitch;
289 dst += 256;
290 }
291 }
292
293 // we now have an 8 bit per pixel grey scale bitmap
294 // that is width wide and pf->ftSize->metrics.y_ppem tall
295
296 glyph.imageHeight = scaled_height;
297 glyph.imageWidth = scaled_width;
298 glyph.s = (float)*xOut / 256;
299 glyph.t = (float)*yOut / 256;
300 glyph.s2 = glyph.s + (float)scaled_width / 256;
301 glyph.t2 = glyph.t + (float)scaled_height / 256;
302
303 *xOut += scaled_width + 1;
304
305 ri.Free(bitmap->buffer);
306 ri.Free(bitmap);
307 }
308
309 return &glyph;
310 }
311 #endif
312
313 static int fdOffset;
314 static byte *fdFile;
315
readInt(void)316 int readInt( void ) {
317 int i = ( (unsigned int)fdFile[fdOffset] | ( (unsigned int)fdFile[fdOffset + 1] << 8 ) | ( (unsigned int)fdFile[fdOffset + 2] << 16) | ( (unsigned int)fdFile[fdOffset + 3] << 24 ) );
318 fdOffset += 4;
319 return i;
320 }
321
322 typedef union {
323 byte fred[4];
324 float ffred;
325 } poor;
326
readFloat(void)327 float readFloat( void ) {
328 poor me;
329 #if defined Q3_BIG_ENDIAN
330 me.fred[0] = fdFile[fdOffset + 3];
331 me.fred[1] = fdFile[fdOffset + 2];
332 me.fred[2] = fdFile[fdOffset + 1];
333 me.fred[3] = fdFile[fdOffset + 0];
334 #elif defined Q3_LITTLE_ENDIAN
335 me.fred[0] = fdFile[fdOffset + 0];
336 me.fred[1] = fdFile[fdOffset + 1];
337 me.fred[2] = fdFile[fdOffset + 2];
338 me.fred[3] = fdFile[fdOffset + 3];
339 #endif
340 fdOffset += 4;
341 return me.ffred;
342 }
343
RE_RegisterFont(const char * fontName,int pointSize,fontInfo_t * font)344 void RE_RegisterFont( const char *fontName, int pointSize, fontInfo_t *font ) {
345 #ifdef BUILD_FREETYPE
346 FT_Face face;
347 int j, k, xOut, yOut, lastStart, imageNumber;
348 int scaledSize, newSize, maxHeight, left;
349 unsigned char *out, *imageBuff;
350 glyphInfo_t *glyph;
351 image_t *image;
352 qhandle_t h;
353 float max;
354 float dpi = 72;
355 float glyphScale;
356 #endif
357 void *faceData;
358 int i, len;
359 char name[1024];
360
361 if (!fontName) {
362 ri.Printf(PRINT_ALL, "RE_RegisterFont: called with empty name\n");
363 return;
364 }
365
366 if ( pointSize <= 0 ) {
367 pointSize = 12;
368 }
369
370 R_IssuePendingRenderCommands();
371
372 if ( registeredFontCount >= MAX_FONTS ) {
373 ri.Printf( PRINT_WARNING, "RE_RegisterFont: Too many fonts registered already.\n" );
374 return;
375 }
376
377 Com_sprintf( name, sizeof( name ), "fonts/fontImage_%i.dat",pointSize );
378 for ( i = 0; i < registeredFontCount; i++ ) {
379 if ( Q_stricmp( name, registeredFont[i].name ) == 0 ) {
380 Com_Memcpy( font, ®isteredFont[i], sizeof( fontInfo_t ) );
381 return;
382 }
383 }
384
385 len = ri.FS_ReadFile( name, NULL );
386 if ( len == sizeof( fontInfo_t ) ) {
387 ri.FS_ReadFile( name, &faceData );
388 fdOffset = 0;
389 fdFile = faceData;
390 for ( i = 0; i < GLYPHS_PER_FONT; i++ ) {
391 font->glyphs[i].height = readInt();
392 font->glyphs[i].top = readInt();
393 font->glyphs[i].bottom = readInt();
394 font->glyphs[i].pitch = readInt();
395 font->glyphs[i].xSkip = readInt();
396 font->glyphs[i].imageWidth = readInt();
397 font->glyphs[i].imageHeight = readInt();
398 font->glyphs[i].s = readFloat();
399 font->glyphs[i].t = readFloat();
400 font->glyphs[i].s2 = readFloat();
401 font->glyphs[i].t2 = readFloat();
402 font->glyphs[i].glyph = readInt();
403 Q_strncpyz(font->glyphs[i].shaderName, (const char *)&fdFile[fdOffset], sizeof(font->glyphs[i].shaderName));
404 fdOffset += sizeof(font->glyphs[i].shaderName);
405 }
406 font->glyphScale = readFloat();
407 Com_Memcpy( font->name, &fdFile[fdOffset], MAX_QPATH );
408
409 // Com_Memcpy(font, faceData, sizeof(fontInfo_t));
410 Q_strncpyz( font->name, name, sizeof( font->name ) );
411 for ( i = GLYPH_START; i <= GLYPH_END; i++ ) {
412 font->glyphs[i].glyph = RE_RegisterShaderNoMip( font->glyphs[i].shaderName );
413 }
414 Com_Memcpy( ®isteredFont[registeredFontCount++], font, sizeof( fontInfo_t ) );
415 ri.FS_FreeFile(faceData);
416 return;
417 }
418
419 #ifndef BUILD_FREETYPE
420 ri.Printf( PRINT_WARNING, "RE_RegisterFont: FreeType code not available\n" ); // JPW NERVE was PRINT_ALL
421 #else
422 if ( ftLibrary == NULL ) {
423 ri.Printf( PRINT_WARNING, "RE_RegisterFont: FreeType not initialized.\n" ); // JPW NERVE was PRINT_ALL
424 return;
425 }
426
427 len = ri.FS_ReadFile( fontName, &faceData );
428 if ( len <= 0 ) {
429 ri.Printf(PRINT_WARNING, "RE_RegisterFont: Unable to read font file '%s'\n", fontName);
430 return;
431 }
432
433 // allocate on the stack first in case we fail
434 if ( FT_New_Memory_Face( ftLibrary, faceData, len, 0, &face ) ) {
435 ri.Printf(PRINT_WARNING, "RE_RegisterFont: FreeType, unable to allocate new face.\n");
436 return;
437 }
438
439
440 if ( FT_Set_Char_Size( face, pointSize << 6, pointSize << 6, dpi, dpi ) ) {
441 ri.Printf(PRINT_WARNING, "RE_RegisterFont: FreeType, unable to set face char size.\n");
442 return;
443 }
444
445 //*font = ®isteredFonts[registeredFontCount++];
446
447 // make a 256x256 image buffer, once it is full, register it, clean it and keep going
448 // until all glyphs are rendered
449
450 out = ri.Z_Malloc( 256 * 256 );
451 if ( out == NULL ) {
452 ri.Printf(PRINT_WARNING, "RE_RegisterFont: ri.Z_Malloc failure during output image creation.\n");
453 return;
454 }
455 Com_Memset( out, 0, 256 * 256 );
456
457 maxHeight = 0;
458
459 for ( i = GLYPH_START; i <= GLYPH_END; i++ ) {
460 RE_ConstructGlyphInfo( out, &xOut, &yOut, &maxHeight, face, (unsigned char)i, qtrue );
461 }
462
463 xOut = 0;
464 yOut = 0;
465 i = GLYPH_START;
466 lastStart = i;
467 imageNumber = 0;
468
469 while ( i <= GLYPH_END + 1 ) {
470
471 if ( i == GLYPH_END + 1 ) {
472 // upload/save current image buffer
473 xOut = yOut = -1;
474 } else {
475 glyph = RE_ConstructGlyphInfo(out, &xOut, &yOut, &maxHeight, face, (unsigned char)i, qfalse);
476 }
477
478 if ( xOut == -1 || yOut == -1 ) {
479 // ran out of room
480 // we need to create an image from the bitmap, set all the handles in the glyphs to this point
481 //
482
483 scaledSize = 256 * 256;
484 newSize = scaledSize * 4;
485 imageBuff = ri.Z_Malloc( newSize );
486 left = 0;
487 max = 0;
488 for ( k = 0; k < ( scaledSize ) ; k++ ) {
489 if ( max < out[k] ) {
490 max = out[k];
491 }
492 }
493
494 if ( max > 0 ) {
495 max = 255 / max;
496 }
497
498 for ( k = 0; k < ( scaledSize ) ; k++ ) {
499 imageBuff[left++] = 255;
500 imageBuff[left++] = 255;
501 imageBuff[left++] = 255;
502
503 imageBuff[left++] = ( (float)out[k] * max );
504 }
505
506 Com_sprintf( name, sizeof( name ), "fonts/fontImage_%i_%i.tga", imageNumber++, pointSize );
507 if ( r_saveFontData->integer ) {
508 WriteTGA( name, imageBuff, 256, 256 );
509 }
510
511 //Com_sprintf (name, sizeof(name), "fonts/fontImage_%i_%i", imageNumber++, pointSize);
512 image = R_CreateImage( name, imageBuff, 256, 256, IMGTYPE_COLORALPHA, IMGFLAG_CLAMPTOEDGE, 0 );
513 h = RE_RegisterShaderFromImage( name, LIGHTMAP_2D, image, qfalse );
514 for ( j = lastStart; j < i; j++ ) {
515 font->glyphs[j].glyph = h;
516 Q_strncpyz( font->glyphs[j].shaderName, name, sizeof( font->glyphs[j].shaderName ) );
517 }
518 lastStart = i;
519 Com_Memset( out, 0, 256 * 256 );
520 xOut = 0;
521 yOut = 0;
522 ri.Free( imageBuff );
523 if ( i == GLYPH_END + 1 )
524 i++;
525 } else {
526 Com_Memcpy( &font->glyphs[i], glyph, sizeof( glyphInfo_t ) );
527 i++;
528 }
529 }
530
531 // change the scale to be relative to 1 based on 72 dpi ( so dpi of 144 means a scale of .5 )
532 glyphScale = 72.0f / dpi;
533
534 // we also need to adjust the scale based on point size relative to 48 points as the ui scaling is based on a 48 point font
535 glyphScale *= 48.0f / pointSize;
536
537 registeredFont[registeredFontCount].glyphScale = glyphScale;
538 font->glyphScale = glyphScale;
539 Com_Memcpy( ®isteredFont[registeredFontCount++], font, sizeof( fontInfo_t ) );
540
541 if ( r_saveFontData->integer ) {
542 ri.FS_WriteFile( va( "fonts/fontImage_%i.dat", pointSize ), font, sizeof( fontInfo_t ) );
543 }
544
545 ri.Free( out );
546
547 ri.FS_FreeFile( faceData );
548 #endif
549 }
550
551
552
R_InitFreeType(void)553 void R_InitFreeType( void ) {
554 #ifdef BUILD_FREETYPE
555 if ( FT_Init_FreeType( &ftLibrary ) ) {
556 ri.Printf(PRINT_WARNING, "R_InitFreeType: Unable to initialize FreeType.\n");
557 }
558 #endif
559 registeredFontCount = 0;
560 }
561
562
R_DoneFreeType(void)563 void R_DoneFreeType( void ) {
564 #ifdef BUILD_FREETYPE
565 if ( ftLibrary ) {
566 FT_Done_FreeType( ftLibrary );
567 ftLibrary = NULL;
568 }
569 #endif
570 registeredFontCount = 0;
571 }
572
573