fontcache.cpp

Go to the documentation of this file.
00001 /* $Id: fontcache.cpp 25583 2013-07-10 19:41:31Z rubidium $ */
00002 
00003 /*
00004  * This file is part of OpenTTD.
00005  * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
00006  * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
00007  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
00008  */
00009 
00012 #include "stdafx.h"
00013 #include "fontcache.h"
00014 #include "fontdetection.h"
00015 #include "blitter/factory.hpp"
00016 #include "core/math_func.hpp"
00017 #include "core/smallmap_type.hpp"
00018 #include "strings_func.h"
00019 #include "zoom_type.h"
00020 #include "gfx_layout.h"
00021 
00022 #include "table/sprites.h"
00023 #include "table/control_codes.h"
00024 #include "table/unicode.h"
00025 
00026 static const int ASCII_LETTERSTART = 32; 
00027 static const int MAX_FONT_SIZE     = 72; 
00028 
00030 static const int _default_font_height[FS_END]   = {10, 6, 18, 10};
00031 static const int _default_font_ascender[FS_END] = { 8, 5, 15,  8};
00032 
00037 FontCache::FontCache(FontSize fs) : parent(FontCache::Get(fs)), fs(fs), height(_default_font_height[fs]),
00038     ascender(_default_font_ascender[fs]), descender(_default_font_ascender[fs] - _default_font_height[fs]),
00039     units_per_em(1)
00040 {
00041   assert(parent == NULL || this->fs == parent->fs);
00042   FontCache::caches[this->fs] = this;
00043   Layouter::ResetFontCache(this->fs);
00044 }
00045 
00047 FontCache::~FontCache()
00048 {
00049   assert(this->fs == parent->fs);
00050   FontCache::caches[this->fs] = this->parent;
00051   Layouter::ResetFontCache(this->fs);
00052 }
00053 
00054 
00060 int GetCharacterHeight(FontSize size)
00061 {
00062   return FontCache::Get(size)->GetHeight();
00063 }
00064 
00065 
00067 class SpriteFontCache : public FontCache {
00068 private:
00069   SpriteID **glyph_to_spriteid_map; 
00070 
00071   void ClearGlyphToSpriteMap();
00072 public:
00073   SpriteFontCache(FontSize fs);
00074   ~SpriteFontCache();
00075   virtual SpriteID GetUnicodeGlyph(WChar key);
00076   virtual void SetUnicodeGlyph(WChar key, SpriteID sprite);
00077   virtual void InitializeUnicodeGlyphMap();
00078   virtual void ClearFontCache() {}
00079   virtual const Sprite *GetGlyph(GlyphID key);
00080   virtual uint GetGlyphWidth(GlyphID key);
00081   virtual bool GetDrawGlyphShadow();
00082   virtual GlyphID MapCharToGlyph(WChar key) { assert(IsPrintable(key)); return SPRITE_GLYPH | key; }
00083   virtual const void *GetFontTable(uint32 tag, size_t &length) { length = 0; return NULL; }
00084 };
00085 
00090 SpriteFontCache::SpriteFontCache(FontSize fs) : FontCache(fs), glyph_to_spriteid_map(NULL)
00091 {
00092   this->InitializeUnicodeGlyphMap();
00093 }
00094 
00098 SpriteFontCache::~SpriteFontCache()
00099 {
00100   this->ClearGlyphToSpriteMap();
00101 }
00102 
00103 SpriteID SpriteFontCache::GetUnicodeGlyph(GlyphID key)
00104 {
00105   if (this->glyph_to_spriteid_map[GB(key, 8, 8)] == NULL) return 0;
00106   return this->glyph_to_spriteid_map[GB(key, 8, 8)][GB(key, 0, 8)];
00107 }
00108 
00109 void SpriteFontCache::SetUnicodeGlyph(GlyphID key, SpriteID sprite)
00110 {
00111   if (this->glyph_to_spriteid_map == NULL) this->glyph_to_spriteid_map = CallocT<SpriteID*>(256);
00112   if (this->glyph_to_spriteid_map[GB(key, 8, 8)] == NULL) this->glyph_to_spriteid_map[GB(key, 8, 8)] = CallocT<SpriteID>(256);
00113   this->glyph_to_spriteid_map[GB(key, 8, 8)][GB(key, 0, 8)] = sprite;
00114 }
00115 
00116 void SpriteFontCache::InitializeUnicodeGlyphMap()
00117 {
00118   /* Clear out existing glyph map if it exists */
00119   this->ClearGlyphToSpriteMap();
00120 
00121   SpriteID base;
00122   switch (this->fs) {
00123     default: NOT_REACHED();
00124     case FS_MONO:   // Use normal as default for mono spaced font, i.e. FALL THROUGH
00125     case FS_NORMAL: base = SPR_ASCII_SPACE;       break;
00126     case FS_SMALL:  base = SPR_ASCII_SPACE_SMALL; break;
00127     case FS_LARGE:  base = SPR_ASCII_SPACE_BIG;   break;
00128   }
00129 
00130   for (uint i = ASCII_LETTERSTART; i < 256; i++) {
00131     SpriteID sprite = base + i - ASCII_LETTERSTART;
00132     if (!SpriteExists(sprite)) continue;
00133     this->SetUnicodeGlyph(i, sprite);
00134     this->SetUnicodeGlyph(i + SCC_SPRITE_START, sprite);
00135   }
00136 
00137   for (uint i = 0; i < lengthof(_default_unicode_map); i++) {
00138     byte key = _default_unicode_map[i].key;
00139     if (key == CLRA) {
00140       /* Clear the glyph. This happens if the glyph at this code point
00141         * is non-standard and should be accessed by an SCC_xxx enum
00142         * entry only. */
00143       this->SetUnicodeGlyph(_default_unicode_map[i].code, 0);
00144     } else {
00145       SpriteID sprite = base + key - ASCII_LETTERSTART;
00146       this->SetUnicodeGlyph(_default_unicode_map[i].code, sprite);
00147     }
00148   }
00149 }
00150 
00154 void SpriteFontCache::ClearGlyphToSpriteMap()
00155 {
00156   if (this->glyph_to_spriteid_map == NULL) return;
00157 
00158   for (uint i = 0; i < 256; i++) {
00159     free(this->glyph_to_spriteid_map[i]);
00160   }
00161   free(this->glyph_to_spriteid_map);
00162   this->glyph_to_spriteid_map = NULL;
00163 }
00164 
00165 const Sprite *SpriteFontCache::GetGlyph(GlyphID key)
00166 {
00167   SpriteID sprite = this->GetUnicodeGlyph(key);
00168   if (sprite == 0) sprite = this->GetUnicodeGlyph('?');
00169   return GetSprite(sprite, ST_FONT);
00170 }
00171 
00172 uint SpriteFontCache::GetGlyphWidth(GlyphID key)
00173 {
00174   SpriteID sprite = this->GetUnicodeGlyph(key);
00175   if (sprite == 0) sprite = this->GetUnicodeGlyph('?');
00176   return SpriteExists(sprite) ? GetSprite(sprite, ST_FONT)->width + (this->fs != FS_NORMAL) : 0;
00177 }
00178 
00179 bool SpriteFontCache::GetDrawGlyphShadow()
00180 {
00181   return false;
00182 }
00183 
00184 /*static */ FontCache *FontCache::caches[FS_END] = { new SpriteFontCache(FS_NORMAL), new SpriteFontCache(FS_SMALL), new SpriteFontCache(FS_LARGE), new SpriteFontCache(FS_MONO) };
00185 
00186 #ifdef WITH_FREETYPE
00187 #include <ft2build.h>
00188 #include FT_FREETYPE_H
00189 #include FT_GLYPH_H
00190 #include FT_TRUETYPE_TABLES_H
00191 
00193 class FreeTypeFontCache : public FontCache {
00194 private:
00195   FT_Face face;  
00196 
00197   typedef SmallMap<uint32, SmallPair<size_t, const void*> > FontTable; 
00198   FontTable font_tables; 
00199 
00201   struct GlyphEntry {
00202     Sprite *sprite; 
00203     byte width;     
00204     bool duplicate; 
00205   };
00206 
00220   GlyphEntry **glyph_to_sprite;
00221 
00222   GlyphEntry *GetGlyphPtr(GlyphID key);
00223   void SetGlyphPtr(GlyphID key, const GlyphEntry *glyph, bool duplicate = false);
00224 
00225 public:
00226   FreeTypeFontCache(FontSize fs, FT_Face face, int pixels);
00227   ~FreeTypeFontCache();
00228   virtual SpriteID GetUnicodeGlyph(WChar key) { return this->parent->GetUnicodeGlyph(key); }
00229   virtual void SetUnicodeGlyph(WChar key, SpriteID sprite) { this->parent->SetUnicodeGlyph(key, sprite); }
00230   virtual void InitializeUnicodeGlyphMap() { this->parent->InitializeUnicodeGlyphMap(); }
00231   virtual void ClearFontCache();
00232   virtual const Sprite *GetGlyph(GlyphID key);
00233   virtual uint GetGlyphWidth(GlyphID key);
00234   virtual bool GetDrawGlyphShadow();
00235   virtual GlyphID MapCharToGlyph(WChar key);
00236   virtual const void *GetFontTable(uint32 tag, size_t &length);
00237 };
00238 
00239 FT_Library _library = NULL;
00240 
00241 FreeTypeSettings _freetype;
00242 
00243 static const byte FACE_COLOUR   = 1;
00244 static const byte SHADOW_COLOUR = 2;
00245 
00252 FreeTypeFontCache::FreeTypeFontCache(FontSize fs, FT_Face face, int pixels) : FontCache(fs), face(face), glyph_to_sprite(NULL)
00253 {
00254   assert(face != NULL);
00255 
00256   if (pixels == 0) {
00257     /* Try to determine a good height based on the minimal height recommended by the font. */
00258     pixels = _default_font_height[this->fs];
00259 
00260     TT_Header *head = (TT_Header *)FT_Get_Sfnt_Table(this->face, ft_sfnt_head);
00261     if (head != NULL) {
00262       /* Font height is minimum height plus the difference between the default
00263        * height for this font size and the small size. */
00264       int diff = _default_font_height[this->fs] - _default_font_height[FS_SMALL];
00265       pixels = Clamp(min(head->Lowest_Rec_PPEM, 20) + diff, _default_font_height[this->fs], MAX_FONT_SIZE);
00266     }
00267   }
00268 
00269   FT_Error err = FT_Set_Pixel_Sizes(this->face, 0, pixels);
00270   if (err == FT_Err_Invalid_Pixel_Size) {
00271 
00272     /* Find nearest size to that requested */
00273     FT_Bitmap_Size *bs = this->face->available_sizes;
00274     int i = this->face->num_fixed_sizes;
00275     int n = bs->height;
00276     for (; --i; bs++) {
00277       if (abs(pixels - bs->height) < abs(pixels - n)) n = bs->height;
00278     }
00279 
00280     FT_Set_Pixel_Sizes(this->face, 0, n);
00281   }
00282 
00283   this->units_per_em = this->face->units_per_EM;
00284   this->ascender     = this->face->size->metrics.ascender >> 6;
00285   this->descender    = this->face->size->metrics.descender >> 6;
00286   this->height       = this->ascender - this->descender;
00287 }
00288 
00296 static void LoadFreeTypeFont(FontSize fs)
00297 {
00298   FreeTypeSubSetting *settings = NULL;
00299   switch (fs) {
00300     default: NOT_REACHED();
00301     case FS_SMALL:  settings = &_freetype.small;  break;
00302     case FS_NORMAL: settings = &_freetype.medium; break;
00303     case FS_LARGE:  settings = &_freetype.large;  break;
00304     case FS_MONO:   settings = &_freetype.mono;   break;
00305   }
00306 
00307   if (StrEmpty(settings->font)) return;
00308 
00309   if (_library == NULL) {
00310     if (FT_Init_FreeType(&_library) != FT_Err_Ok) {
00311       ShowInfoF("Unable to initialize FreeType, using sprite fonts instead");
00312       return;
00313     }
00314 
00315     DEBUG(freetype, 2, "Initialized");
00316   }
00317 
00318   FT_Face face = NULL;
00319   FT_Error error = FT_New_Face(_library, settings->font, 0, &face);
00320 
00321   if (error != FT_Err_Ok) error = GetFontByFaceName(settings->font, &face);
00322 
00323   if (error == FT_Err_Ok) {
00324     DEBUG(freetype, 2, "Requested '%s', using '%s %s'", settings->font, face->family_name, face->style_name);
00325 
00326     /* Attempt to select the unicode character map */
00327     error = FT_Select_Charmap(face, ft_encoding_unicode);
00328     if (error == FT_Err_Ok) goto found_face; // Success
00329 
00330     if (error == FT_Err_Invalid_CharMap_Handle) {
00331       /* Try to pick a different character map instead. We default to
00332        * the first map, but platform_id 0 encoding_id 0 should also
00333        * be unicode (strange system...) */
00334       FT_CharMap found = face->charmaps[0];
00335       int i;
00336 
00337       for (i = 0; i < face->num_charmaps; i++) {
00338         FT_CharMap charmap = face->charmaps[i];
00339         if (charmap->platform_id == 0 && charmap->encoding_id == 0) {
00340           found = charmap;
00341         }
00342       }
00343 
00344       if (found != NULL) {
00345         error = FT_Set_Charmap(face, found);
00346         if (error == FT_Err_Ok) goto found_face;
00347       }
00348     }
00349   }
00350 
00351   FT_Done_Face(face);
00352 
00353   static const char *SIZE_TO_NAME[] = { "medium", "small", "large", "mono" };
00354   ShowInfoF("Unable to use '%s' for %s font, FreeType reported error 0x%X, using sprite font instead", settings->font, SIZE_TO_NAME[fs], error);
00355   return;
00356 
00357 found_face:
00358   new FreeTypeFontCache(fs, face, settings->size);
00359 }
00360 
00361 
00365 FreeTypeFontCache::~FreeTypeFontCache()
00366 {
00367   FT_Done_Face(this->face);
00368   this->ClearFontCache();
00369 
00370   for (FontTable::iterator iter = this->font_tables.Begin(); iter != this->font_tables.End(); iter++) {
00371     free(iter->second.second);
00372   }
00373 }
00374 
00378 void FreeTypeFontCache::ClearFontCache()
00379 {
00380   if (this->glyph_to_sprite == NULL) return;
00381 
00382   for (int i = 0; i < 256; i++) {
00383     if (this->glyph_to_sprite[i] == NULL) continue;
00384 
00385     for (int j = 0; j < 256; j++) {
00386       if (this->glyph_to_sprite[i][j].duplicate) continue;
00387       free(this->glyph_to_sprite[i][j].sprite);
00388     }
00389 
00390     free(this->glyph_to_sprite[i]);
00391   }
00392 
00393   free(this->glyph_to_sprite);
00394   this->glyph_to_sprite = NULL;
00395 }
00396 
00397 FreeTypeFontCache::GlyphEntry *FreeTypeFontCache::GetGlyphPtr(GlyphID key)
00398 {
00399   if (this->glyph_to_sprite == NULL) return NULL;
00400   if (this->glyph_to_sprite[GB(key, 8, 8)] == NULL) return NULL;
00401   return &this->glyph_to_sprite[GB(key, 8, 8)][GB(key, 0, 8)];
00402 }
00403 
00404 
00405 void FreeTypeFontCache::SetGlyphPtr(GlyphID key, const GlyphEntry *glyph, bool duplicate)
00406 {
00407   if (this->glyph_to_sprite == NULL) {
00408     DEBUG(freetype, 3, "Allocating root glyph cache for size %u", this->fs);
00409     this->glyph_to_sprite = CallocT<GlyphEntry*>(256);
00410   }
00411 
00412   if (this->glyph_to_sprite[GB(key, 8, 8)] == NULL) {
00413     DEBUG(freetype, 3, "Allocating glyph cache for range 0x%02X00, size %u", GB(key, 8, 8), this->fs);
00414     this->glyph_to_sprite[GB(key, 8, 8)] = CallocT<GlyphEntry>(256);
00415   }
00416 
00417   DEBUG(freetype, 4, "Set glyph for unicode character 0x%04X, size %u", key, this->fs);
00418   this->glyph_to_sprite[GB(key, 8, 8)][GB(key, 0, 8)].sprite    = glyph->sprite;
00419   this->glyph_to_sprite[GB(key, 8, 8)][GB(key, 0, 8)].width     = glyph->width;
00420   this->glyph_to_sprite[GB(key, 8, 8)][GB(key, 0, 8)].duplicate = duplicate;
00421 }
00422 
00423 static void *AllocateFont(size_t size)
00424 {
00425   return MallocT<byte>(size);
00426 }
00427 
00428 
00429 /* Check if a glyph should be rendered with antialiasing */
00430 static bool GetFontAAState(FontSize size)
00431 {
00432   /* AA is only supported for 32 bpp */
00433   if (BlitterFactoryBase::GetCurrentBlitter()->GetScreenDepth() != 32) return false;
00434 
00435   switch (size) {
00436     default: NOT_REACHED();
00437     case FS_NORMAL: return _freetype.medium.aa;
00438     case FS_SMALL:  return _freetype.small.aa;
00439     case FS_LARGE:  return _freetype.large.aa;
00440     case FS_MONO:   return _freetype.mono.aa;
00441   }
00442 }
00443 
00444 
00445 const Sprite *FreeTypeFontCache::GetGlyph(GlyphID key)
00446 {
00447   if ((key & SPRITE_GLYPH) != 0) return parent->GetGlyph(key);
00448 
00449   /* Check for the glyph in our cache */
00450   GlyphEntry *glyph = this->GetGlyphPtr(key);
00451   if (glyph != NULL && glyph->sprite != NULL) return glyph->sprite;
00452 
00453   FT_GlyphSlot slot = this->face->glyph;
00454 
00455   bool aa = GetFontAAState(this->fs);
00456 
00457   GlyphEntry new_glyph;
00458   if (key == 0) {
00459     GlyphID question_glyph = this->MapCharToGlyph('?');
00460     if (question_glyph == 0) {
00461       /* The font misses the '?' character. Use sprite font. */
00462       SpriteID sprite = this->GetUnicodeGlyph(key);
00463       Sprite *spr = (Sprite*)GetRawSprite(sprite, ST_FONT, AllocateFont);
00464       assert(spr != NULL);
00465       new_glyph.sprite = spr;
00466       new_glyph.width  = spr->width + (this->fs != FS_NORMAL);
00467       this->SetGlyphPtr(key, &new_glyph, false);
00468       return new_glyph.sprite;
00469     } else {
00470       /* Use '?' for missing characters. */
00471       this->GetGlyph(question_glyph);
00472       glyph = this->GetGlyphPtr(question_glyph);
00473       this->SetGlyphPtr(key, glyph, true);
00474       return glyph->sprite;
00475     }
00476   }
00477   FT_Load_Glyph(this->face, key, FT_LOAD_DEFAULT);
00478   FT_Render_Glyph(this->face->glyph, aa ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO);
00479 
00480   /* Despite requesting a normal glyph, FreeType may have returned a bitmap */
00481   aa = (slot->bitmap.pixel_mode == FT_PIXEL_MODE_GRAY);
00482 
00483   /* Add 1 pixel for the shadow on the medium font. Our sprite must be at least 1x1 pixel */
00484   int width  = max(1, slot->bitmap.width + (this->fs == FS_NORMAL));
00485   int height = max(1, slot->bitmap.rows  + (this->fs == FS_NORMAL));
00486 
00487   /* Limit glyph size to prevent overflows later on. */
00488   if (width > 256 || height > 256) usererror("Font glyph is too large");
00489 
00490   /* FreeType has rendered the glyph, now we allocate a sprite and copy the image into it */
00491   SpriteLoader::Sprite sprite;
00492   sprite.AllocateData(ZOOM_LVL_NORMAL, width * height);
00493   sprite.type = ST_FONT;
00494   sprite.width = width;
00495   sprite.height = height;
00496   sprite.x_offs = slot->bitmap_left;
00497   sprite.y_offs = this->ascender - slot->bitmap_top;
00498 
00499   /* Draw shadow for medium size */
00500   if (this->fs == FS_NORMAL && !aa) {
00501     for (int y = 0; y < slot->bitmap.rows; y++) {
00502       for (int x = 0; x < slot->bitmap.width; x++) {
00503         if (aa ? (slot->bitmap.buffer[x + y * slot->bitmap.pitch] > 0) : HasBit(slot->bitmap.buffer[(x / 8) + y * slot->bitmap.pitch], 7 - (x % 8))) {
00504           sprite.data[1 + x + (1 + y) * sprite.width].m = SHADOW_COLOUR;
00505           sprite.data[1 + x + (1 + y) * sprite.width].a = aa ? slot->bitmap.buffer[x + y * slot->bitmap.pitch] : 0xFF;
00506         }
00507       }
00508     }
00509   }
00510 
00511   for (int y = 0; y < slot->bitmap.rows; y++) {
00512     for (int x = 0; x < slot->bitmap.width; x++) {
00513       if (aa ? (slot->bitmap.buffer[x + y * slot->bitmap.pitch] > 0) : HasBit(slot->bitmap.buffer[(x / 8) + y * slot->bitmap.pitch], 7 - (x % 8))) {
00514         sprite.data[x + y * sprite.width].m = FACE_COLOUR;
00515         sprite.data[x + y * sprite.width].a = aa ? slot->bitmap.buffer[x + y * slot->bitmap.pitch] : 0xFF;
00516       }
00517     }
00518   }
00519 
00520   new_glyph.sprite = BlitterFactoryBase::GetCurrentBlitter()->Encode(&sprite, AllocateFont);
00521   new_glyph.width  = slot->advance.x >> 6;
00522 
00523   this->SetGlyphPtr(key, &new_glyph);
00524 
00525   return new_glyph.sprite;
00526 }
00527 
00528 
00529 bool FreeTypeFontCache::GetDrawGlyphShadow()
00530 {
00531   return this->fs == FS_NORMAL && GetFontAAState(FS_NORMAL);
00532 }
00533 
00534 
00535 uint FreeTypeFontCache::GetGlyphWidth(GlyphID key)
00536 {
00537   if ((key & SPRITE_GLYPH) != 0) return this->parent->GetGlyphWidth(key);
00538 
00539   GlyphEntry *glyph = this->GetGlyphPtr(key);
00540   if (glyph == NULL || glyph->sprite == NULL) {
00541     this->GetGlyph(key);
00542     glyph = this->GetGlyphPtr(key);
00543   }
00544 
00545   return glyph->width;
00546 }
00547 
00548 GlyphID FreeTypeFontCache::MapCharToGlyph(WChar key)
00549 {
00550   assert(IsPrintable(key));
00551 
00552   if (key >= SCC_SPRITE_START && key <= SCC_SPRITE_END) {
00553     return this->parent->MapCharToGlyph(key);
00554   }
00555 
00556   return FT_Get_Char_Index(this->face, key);
00557 }
00558 
00559 const void *FreeTypeFontCache::GetFontTable(uint32 tag, size_t &length)
00560 {
00561   const FontTable::iterator iter = this->font_tables.Find(tag);
00562   if (iter != this->font_tables.End()) {
00563     length = iter->second.first;
00564     return iter->second.second;
00565   }
00566 
00567   FT_ULong len = 0;
00568   FT_Byte *result = NULL;
00569 
00570   FT_Load_Sfnt_Table(this->face, tag, 0, NULL, &len);
00571 
00572   if (len > 0) {
00573     result = MallocT<FT_Byte>(len);
00574     FT_Load_Sfnt_Table(this->face, tag, 0, result, &len);
00575   }
00576   length = len;
00577 
00578   this->font_tables.Insert(tag, SmallPair<size_t, const void *>(length, result));
00579   return result;
00580 }
00581 
00582 #endif /* WITH_FREETYPE */
00583 
00588 void InitFreeType(bool monospace)
00589 {
00590   for (FontSize fs = FS_BEGIN; fs < FS_END; fs++) {
00591     if (monospace != (fs == FS_MONO)) continue;
00592 
00593     FontCache *fc = FontCache::Get(fs);
00594     if (fc->HasParent()) delete fc;
00595 
00596 #ifdef WITH_FREETYPE
00597     LoadFreeTypeFont(fs);
00598 #endif
00599   }
00600 }
00601 
00605 void UninitFreeType()
00606 {
00607   for (FontSize fs = FS_BEGIN; fs < FS_END; fs++) {
00608     FontCache *fc = FontCache::Get(fs);
00609     if (fc->HasParent()) delete fc;
00610   }
00611 
00612 #ifdef WITH_FREETYPE
00613   FT_Done_FreeType(_library);
00614   _library = NULL;
00615 #endif /* WITH_FREETYPE */
00616 }