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, &registeredFont[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( &registeredFont[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 = &registeredFonts[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( &registeredFont[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