fontcache.cpp

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