| /******************************************************************************* | |
| * TrueType font-related functions for Wine PostScript driver. Currently just | |
| * uses FreeType to read font metrics. | |
| * | |
| * Copyright 2001 Ian Pilcher | |
| * | |
| * This library is free software; you can redistribute it and/or | |
| * modify it under the terms of the GNU Lesser General Public | |
| * License as published by the Free Software Foundation; either | |
| * version 2.1 of the License, or (at your option) any later version. | |
| * | |
| * This library 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. See the GNU | |
| * Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General Public | |
| * License along with this library; if not, write to the Free Software | |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
| * | |
| * NOTE: Many of the functions in this file can return either fatal errors | |
| * (memory allocation failure or unexpected FreeType error) or non-fatal | |
| * errors (unusable font file). Fatal errors are indicated by returning | |
| * FALSE; see individual function descriptions for how they indicate non- | |
| * fatal errors. | |
| * | |
| */ | |
| #include "config.h" | |
| #include "wine/port.h" | |
| #ifdef HAVE_FREETYPE | |
| /* | |
| * These stupid #ifdefs should work for FreeType 2.0.1 and 2.0.2. Beyond that | |
| * is anybody's guess. | |
| */ | |
| #ifdef HAVE_FREETYPE_FREETYPE_H | |
| #include <freetype/freetype.h> | |
| #endif | |
| #ifdef HAVE_FREETYPE_FTGLYPH_H | |
| #include <freetype/ftglyph.h> | |
| #endif | |
| #ifdef HAVE_FREETYPE_TTTABLES_H | |
| #include <freetype/tttables.h> | |
| #endif | |
| #ifdef HAVE_FREETYPE_FTSNAMES_H | |
| #include <freetype/ftsnames.h> | |
| #else | |
| # ifdef HAVE_FREETYPE_FTNAMES_H | |
| # include <freetype/ftnames.h> | |
| # endif | |
| #endif | |
| #ifdef HAVE_FREETYPE_TTNAMEID_H | |
| #include <freetype/ttnameid.h> | |
| #endif | |
| #include <sys/types.h> | |
| #include <dirent.h> | |
| #include <string.h> | |
| #include <stdio.h> | |
| #include <errno.h> | |
| #include "winnt.h" | |
| #include "winerror.h" | |
| #include "winreg.h" | |
| #include "psdrv.h" | |
| #include "wine/debug.h" | |
| WINE_DEFAULT_DEBUG_CHANNEL(psdrv); | |
| #define REQUIRED_FACE_FLAGS ( FT_FACE_FLAG_SCALABLE | \ | |
| FT_FACE_FLAG_HORIZONTAL | \ | |
| FT_FACE_FLAG_SFNT | \ | |
| FT_FACE_FLAG_GLYPH_NAMES ) | |
| #define GLYPH_LOAD_FLAGS ( FT_LOAD_NO_SCALE | \ | |
| FT_LOAD_IGNORE_TRANSFORM | \ | |
| FT_LOAD_LINEAR_DESIGN ) | |
| #ifndef SONAME_LIBFREETYPE | |
| #define SONAME_LIBFREETYPE "libfreetype.so" | |
| #endif | |
| static void *ft_handle = NULL; | |
| #define MAKE_FUNCPTR(f) static typeof(f) * p##f = NULL; | |
| MAKE_FUNCPTR(FT_Done_Face) | |
| MAKE_FUNCPTR(FT_Done_FreeType) | |
| MAKE_FUNCPTR(FT_Get_Char_Index) | |
| MAKE_FUNCPTR(FT_Get_Glyph_Name) | |
| MAKE_FUNCPTR(FT_Get_Sfnt_Name) | |
| MAKE_FUNCPTR(FT_Get_Sfnt_Name_Count) | |
| MAKE_FUNCPTR(FT_Get_Sfnt_Table) | |
| MAKE_FUNCPTR(FT_Init_FreeType) | |
| MAKE_FUNCPTR(FT_Load_Glyph) | |
| MAKE_FUNCPTR(FT_New_Face) | |
| MAKE_FUNCPTR(FT_Set_Charmap) | |
| #undef MAKE_FUNCPTR | |
| /******************************************************************************* | |
| * FindCharMap | |
| * | |
| * Finds Windows character map and creates "EncodingScheme" string. Returns | |
| * FALSE to indicate memory allocation or FreeType error; sets *p_charmap to | |
| * NULL if no Windows encoding is present. | |
| * | |
| * Returns Unicode character map if present; otherwise uses the first Windows | |
| * character map found. | |
| * | |
| */ | |
| static const LPCSTR encoding_names[7] = | |
| { | |
| "WindowsSymbol", /* TT_MS_ID_SYMBOL_CS */ | |
| "WindowsUnicode", /* TT_MS_ID_UNICODE_CS */ | |
| "WindowsShiftJIS", /* TT_MS_ID_SJIS */ | |
| "WindowsPRC", /* TT_MS_ID_GB2312 */ | |
| "WindowsBig5", /* TT_MS_ID_BIG_5 */ | |
| "WindowsWansung", /* TT_MS_ID_WANSUNG */ | |
| "WindowsJohab" /* TT_MS_ID_JOHAB */ | |
| /* "WindowsUnknown65535" is the longest possible (encoding_id is a UShort) */ | |
| }; | |
| static BOOL FindCharMap(FT_Face face, FT_CharMap *p_charmap, LPSTR *p_sz) | |
| { | |
| FT_Int i; | |
| FT_Error error; | |
| FT_CharMap charmap = NULL; | |
| for (i = 0; i < face->num_charmaps; ++i) | |
| { | |
| if (face->charmaps[i]->platform_id != TT_PLATFORM_MICROSOFT) | |
| continue; | |
| if (face->charmaps[i]->encoding_id == TT_MS_ID_UNICODE_CS) | |
| { | |
| charmap = face->charmaps[i]; | |
| break; | |
| } | |
| if (charmap == NULL) | |
| charmap = face->charmaps[i]; | |
| } | |
| *p_charmap = charmap; | |
| if (charmap == NULL) | |
| { | |
| WARN("No Windows character map found\n"); | |
| return TRUE; | |
| } | |
| error = pFT_Set_Charmap(face, charmap); | |
| if (error != FT_Err_Ok) | |
| { | |
| ERR("%s returned %i\n", "FT_Set_Charmap", error); | |
| return FALSE; | |
| } | |
| *p_sz = HeapAlloc(PSDRV_Heap, 0, sizeof("WindowsUnknown65535")); | |
| if (*p_sz == NULL) | |
| return FALSE; | |
| if (charmap->encoding_id < 7) | |
| strcpy(*p_sz, encoding_names[charmap->encoding_id]); | |
| else | |
| sprintf(*p_sz, "%s%u", "WindowsUnknown", charmap->encoding_id); | |
| return TRUE; | |
| } | |
| /******************************************************************************* | |
| * MSTTStrToSz | |
| * | |
| * Converts a string in the TrueType NAME table to a null-terminated ASCII | |
| * character string. Space for the string is allocated from the driver heap. | |
| * Only handles platform_id = 3 (TT_PLATFORM_MICROSOFT) strings (16-bit, big | |
| * endian). It also only handles ASCII character codes (< 128). | |
| * | |
| * Sets *p_sz to NULL if string cannot be converted; only returns FALSE for | |
| * memory allocation failure. | |
| * | |
| */ | |
| static BOOL MSTTStrToSz(const FT_SfntName *name, LPSTR *p_sz) | |
| { | |
| FT_UShort i; | |
| INT len; | |
| BYTE *wsz; | |
| LPSTR sz; | |
| len = name->string_len / 2; /* # of 16-bit chars */ | |
| *p_sz = sz = HeapAlloc(PSDRV_Heap, 0, len + 1); | |
| if (sz == NULL) | |
| return FALSE; | |
| wsz = (BYTE *)name->string; | |
| for (i = 0; i < len; ++i, ++sz) | |
| { | |
| USHORT wc = (wsz[0] << 8) + wsz[1]; | |
| wsz += 2; | |
| if (wc > 127) | |
| { | |
| WARN("Non-ASCII character 0x%.4x\n", wc); | |
| HeapFree(PSDRV_Heap, 0, *p_sz); | |
| *p_sz = NULL; | |
| return TRUE; | |
| } | |
| *sz = (CHAR)wc; | |
| } | |
| *sz = '\0'; | |
| return TRUE; | |
| } | |
| /******************************************************************************* | |
| * FindMSTTString | |
| * | |
| * Finds the requested Microsoft platform string in the TrueType NAME table and | |
| * converts it to a null-terminated ASCII string. Currently looks for U.S. | |
| * English names only. | |
| * | |
| * Sets string to NULL if not present or cannot be converted; returns FALSE | |
| * only for memory allocation failure. | |
| * | |
| */ | |
| static BOOL FindMSTTString(FT_Face face, FT_CharMap charmap, FT_UShort name_id, | |
| LPSTR *p_sz) | |
| { | |
| FT_UInt num_strings, string_index; | |
| FT_SfntName name; | |
| FT_Error error; | |
| num_strings = pFT_Get_Sfnt_Name_Count(face); | |
| for (string_index = 0; string_index < num_strings; ++string_index) | |
| { | |
| error = pFT_Get_Sfnt_Name(face, string_index, &name); | |
| if (error != FT_Err_Ok) | |
| { | |
| ERR("%s returned %i\n", "FT_Get_Sfnt_Name", error); | |
| return FALSE; | |
| } | |
| /* FIXME - Handle other languages? */ | |
| if (name.platform_id != TT_PLATFORM_MICROSOFT || | |
| name.language_id != TT_MS_LANGID_ENGLISH_UNITED_STATES) | |
| continue; | |
| if (name.platform_id != charmap->platform_id || | |
| name.encoding_id != charmap->encoding_id) | |
| continue; | |
| if (name.name_id != name_id) | |
| continue; | |
| return MSTTStrToSz(&name, p_sz); | |
| } | |
| *p_sz = NULL; /* didn't find it */ | |
| return TRUE; | |
| } | |
| /******************************************************************************* | |
| * PSUnits | |
| * | |
| * Convert TrueType font units (relative to font em square) to PostScript | |
| * units. | |
| * | |
| */ | |
| inline static float PSUnits(LONG x, USHORT em_size) | |
| { | |
| return 1000.0 * (float)x / (float)em_size; | |
| } | |
| /******************************************************************************* | |
| * StartAFM | |
| * | |
| * Allocates space for the AFM on the driver heap and reads basic font metrics | |
| * from the HEAD, POST, HHEA, and OS/2 tables. Returns FALSE for memory | |
| * allocation error; sets *p_afm to NULL if required information is missing. | |
| * | |
| */ | |
| static BOOL StartAFM(FT_Face face, AFM **p_afm) | |
| { | |
| TT_Header *head; | |
| TT_Postscript *post; | |
| TT_OS2 *os2; | |
| TT_HoriHeader *hhea; | |
| USHORT em_size; | |
| AFM *afm; | |
| head = pFT_Get_Sfnt_Table(face, ft_sfnt_head); | |
| post = pFT_Get_Sfnt_Table(face, ft_sfnt_post); | |
| os2 = pFT_Get_Sfnt_Table(face, ft_sfnt_os2); | |
| hhea = pFT_Get_Sfnt_Table(face, ft_sfnt_hhea); | |
| if (head == NULL || post == NULL || os2 == NULL || hhea == NULL || | |
| os2->version == 0xffff) /* old Macintosh font */ | |
| { | |
| WARN("Required table(s) missing\n"); | |
| *p_afm = NULL; | |
| return TRUE; | |
| } | |
| *p_afm = afm = HeapAlloc(PSDRV_Heap, 0, sizeof(*afm)); | |
| if (afm == NULL) | |
| return FALSE; | |
| afm->WinMetrics.usUnitsPerEm = em_size = head->Units_Per_EM; | |
| afm->WinMetrics.sAscender = hhea->Ascender; | |
| afm->WinMetrics.sDescender = hhea->Descender; | |
| afm->WinMetrics.sLineGap = hhea->Line_Gap; | |
| afm->WinMetrics.sTypoAscender = os2->sTypoAscender; | |
| afm->WinMetrics.sTypoDescender = os2->sTypoDescender; | |
| afm->WinMetrics.sTypoLineGap = os2->sTypoLineGap; | |
| afm->WinMetrics.usWinAscent = os2->usWinAscent; | |
| afm->WinMetrics.usWinDescent = os2->usWinDescent; | |
| afm->WinMetrics.sAvgCharWidth = os2->xAvgCharWidth; | |
| afm->Weight = os2->usWeightClass; | |
| afm->ItalicAngle = ((float)(post->italicAngle)) / 65536.0; | |
| afm->IsFixedPitch = (post-> isFixedPitch == 0) ? FALSE : TRUE; | |
| afm->UnderlinePosition = PSUnits(post->underlinePosition, em_size); | |
| afm->UnderlineThickness = PSUnits(post->underlineThickness, em_size); | |
| afm->FontBBox.llx = PSUnits(head->xMin, em_size); | |
| afm->FontBBox.lly = PSUnits(head->yMin, em_size); | |
| afm->FontBBox.urx = PSUnits(head->xMax, em_size); | |
| afm->FontBBox.ury = PSUnits(head->yMax, em_size); | |
| afm->Ascender = PSUnits(os2->sTypoAscender, em_size); | |
| afm->Descender = PSUnits(os2->sTypoDescender, em_size); | |
| return TRUE; | |
| } | |
| /******************************************************************************* | |
| * ReadCharMetrics | |
| * | |
| * Reads metrics for each glyph in a TrueType font. Returns false for memory | |
| * allocation or FreeType error; sets *p_metrics to NULL for non-fatal error. | |
| * | |
| */ | |
| static BOOL ReadCharMetrics(FT_Face face, AFM *afm, AFMMETRICS **p_metrics) | |
| { | |
| FT_ULong charcode, index; | |
| AFMMETRICS *metrics; | |
| USHORT em_size = afm->WinMetrics.usUnitsPerEm; | |
| for (charcode = 0, index = 0; charcode < 65536; ++charcode) | |
| if (pFT_Get_Char_Index(face, charcode) != 0) | |
| ++index; /* count # of glyphs */ | |
| afm->NumofMetrics = index; | |
| *p_metrics = metrics = HeapAlloc(PSDRV_Heap, 0, index * sizeof(*metrics)); | |
| if (metrics == NULL) | |
| return FALSE; | |
| for (charcode = 0, index = 0; charcode < 65536; ++charcode) | |
| { | |
| FT_UInt glyph_index = pFT_Get_Char_Index(face, charcode); | |
| FT_Error error; | |
| CHAR buffer[128]; /* for glyph names */ | |
| if (glyph_index == 0) | |
| continue; | |
| error = pFT_Load_Glyph(face, glyph_index, GLYPH_LOAD_FLAGS); | |
| if (error != FT_Err_Ok) | |
| { | |
| ERR("%s returned %i\n", "FT_Load_Glyph", error); | |
| goto cleanup; | |
| } | |
| error = pFT_Get_Glyph_Name(face, glyph_index, buffer, sizeof(buffer)); | |
| if (error != FT_Err_Ok) | |
| { | |
| ERR("%s returned %i\n", "FT_Get_Glyph_Name", error); | |
| goto cleanup; | |
| } | |
| metrics[index].N = PSDRV_GlyphName(buffer); | |
| if (metrics[index].N == NULL) | |
| goto cleanup; | |
| metrics[index].C = metrics[index].UV = charcode; | |
| metrics[index].WX = PSUnits(face->glyph->metrics.horiAdvance, em_size); | |
| ++index; | |
| } | |
| if (afm->WinMetrics.sAvgCharWidth == 0) | |
| afm->WinMetrics.sAvgCharWidth = PSDRV_CalcAvgCharWidth(afm); | |
| return TRUE; | |
| cleanup: | |
| HeapFree(PSDRV_Heap, 0, metrics); | |
| return FALSE; | |
| } | |
| /******************************************************************************* | |
| * BuildTrueTypeAFM | |
| * | |
| * Builds the AFM for a TrueType font and adds it to the driver font list. | |
| * Returns FALSE only on an unexpected error (memory allocation failure or | |
| * FreeType error). | |
| * | |
| */ | |
| static BOOL BuildTrueTypeAFM(FT_Face face) | |
| { | |
| AFM *afm; | |
| AFMMETRICS *metrics; | |
| LPSTR font_name, full_name, family_name, encoding_scheme; | |
| FT_CharMap charmap; | |
| BOOL retval, added; | |
| retval = StartAFM(face, &afm); | |
| if (retval == FALSE || afm == NULL) | |
| return retval; | |
| retval = FindCharMap(face, &charmap, &encoding_scheme); | |
| if (retval == FALSE || charmap == NULL) | |
| goto cleanup_afm; | |
| retval = FindMSTTString(face, charmap, TT_NAME_ID_PS_NAME, &font_name); | |
| if (retval == FALSE || font_name == NULL) | |
| goto cleanup_encoding_scheme; | |
| retval = FindMSTTString(face, charmap, TT_NAME_ID_FULL_NAME, &full_name); | |
| if (retval == FALSE || full_name == NULL) | |
| goto cleanup_font_name; | |
| retval = FindMSTTString(face, charmap, TT_NAME_ID_FONT_FAMILY, | |
| &family_name); | |
| if (retval == FALSE || family_name == NULL) | |
| goto cleanup_full_name; | |
| retval = ReadCharMetrics(face, afm, &metrics); | |
| if (retval == FALSE || metrics == NULL) | |
| goto cleanup_family_name; | |
| afm->EncodingScheme = encoding_scheme; afm->FontName = font_name; | |
| afm->FullName = full_name; afm->FamilyName = family_name; | |
| afm->Metrics = metrics; | |
| retval = PSDRV_AddAFMtoList(&PSDRV_AFMFontList, afm, &added); | |
| if (retval == FALSE || added == FALSE) | |
| goto cleanup_family_name; | |
| return TRUE; | |
| /* clean up after fatal or non-fatal errors */ | |
| cleanup_family_name: | |
| HeapFree(PSDRV_Heap, 0, family_name); | |
| cleanup_full_name: | |
| HeapFree(PSDRV_Heap, 0, full_name); | |
| cleanup_font_name: | |
| HeapFree(PSDRV_Heap, 0, font_name); | |
| cleanup_encoding_scheme: | |
| HeapFree(PSDRV_Heap, 0, encoding_scheme); | |
| cleanup_afm: | |
| HeapFree(PSDRV_Heap, 0, afm); | |
| return retval; | |
| } | |
| /******************************************************************************* | |
| * ReadTrueTypeFile | |
| * | |
| * Reads font metrics from TrueType font file. Only returns FALSE for | |
| * unexpected errors (memory allocation failure or FreeType error). | |
| * | |
| */ | |
| static BOOL ReadTrueTypeFile(FT_Library library, LPCSTR filename) | |
| { | |
| FT_Error error; | |
| FT_Face face; | |
| TRACE("%s\n", filename); | |
| error = pFT_New_Face(library, filename, 0, &face); | |
| if (error != FT_Err_Ok) | |
| { | |
| WARN("FreeType error %i opening %s\n", error, filename); | |
| return TRUE; | |
| } | |
| if ((face->face_flags & REQUIRED_FACE_FLAGS) == REQUIRED_FACE_FLAGS) | |
| { | |
| if (BuildTrueTypeAFM(face) == FALSE) | |
| { | |
| pFT_Done_Face(face); | |
| return FALSE; | |
| } | |
| } | |
| else | |
| { | |
| WARN("Required information missing from %s\n", filename); | |
| } | |
| error = pFT_Done_Face(face); | |
| if (error != FT_Err_Ok) | |
| { | |
| ERR("%s returned %i\n", "FT_Done_Face", error); | |
| return FALSE; | |
| } | |
| return TRUE; | |
| } | |
| /******************************************************************************* | |
| * ReadTrueTypeDir | |
| * | |
| * Reads all TrueType font files in a directory. | |
| * | |
| */ | |
| static BOOL ReadTrueTypeDir(FT_Library library, LPCSTR dirname) | |
| { | |
| struct dirent *dent; | |
| DIR *dir; | |
| CHAR filename[256]; | |
| dir = opendir(dirname); | |
| if (dir == NULL) | |
| { | |
| WARN("'%s' opening %s\n", strerror(errno), dirname); | |
| return TRUE; | |
| } | |
| while ((dent = readdir(dir)) != NULL) | |
| { | |
| CHAR *file_extension = strrchr(dent->d_name, '.'); | |
| int fn_len; | |
| if (file_extension == NULL || strcasecmp(file_extension, ".ttf") != 0) | |
| continue; | |
| fn_len = snprintf(filename, 256, "%s/%s", dirname, dent->d_name); | |
| if (fn_len < 0 || fn_len > sizeof(filename) - 1) | |
| { | |
| WARN("Path '%s/%s' is too long\n", dirname, dent->d_name); | |
| continue; | |
| } | |
| if (ReadTrueTypeFile(library, filename) == FALSE) | |
| { | |
| closedir(dir); | |
| return FALSE; | |
| } | |
| } | |
| closedir(dir); | |
| return TRUE; | |
| } | |
| /******************************************************************************* | |
| * PSDRV_GetTrueTypeMetrics | |
| * | |
| * Reads font metrics from TrueType font files in directories listed in the | |
| * [TrueType Font Directories] section of the Wine configuration file. | |
| * | |
| * If this function fails (returns FALSE), the driver will fail to initialize | |
| * and the driver heap will be destroyed, so it's not necessary to HeapFree | |
| * everything in that event. | |
| * | |
| */ | |
| BOOL PSDRV_GetTrueTypeMetrics(void) | |
| { | |
| CHAR name_buf[256], value_buf[256]; | |
| INT i = 0; | |
| FT_Error error; | |
| FT_Library library; | |
| HKEY hkey; | |
| DWORD type, name_len, value_len; | |
| if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, | |
| "Software\\Wine\\Wine\\Config\\TrueType Font Directories", | |
| 0, KEY_READ, &hkey) != ERROR_SUCCESS) | |
| return TRUE; | |
| ft_handle = wine_dlopen(SONAME_LIBFREETYPE, RTLD_NOW, NULL, 0); | |
| if(!ft_handle) { | |
| WINE_MESSAGE( | |
| "Wine cannot find the FreeType font library. To enable Wine to\n" | |
| "use TrueType fonts please install a version of FreeType greater than\n" | |
| "or equal to 2.0.5.\n" | |
| "http://www.freetype.org\n"); | |
| return TRUE; | |
| } | |
| #define LOAD_FUNCPTR(f) if((p##f = wine_dlsym(ft_handle, #f, NULL, 0)) == NULL) goto sym_not_found; | |
| LOAD_FUNCPTR(FT_Done_Face) | |
| LOAD_FUNCPTR(FT_Done_FreeType) | |
| LOAD_FUNCPTR(FT_Get_Char_Index) | |
| LOAD_FUNCPTR(FT_Get_Glyph_Name) | |
| LOAD_FUNCPTR(FT_Get_Sfnt_Name) | |
| LOAD_FUNCPTR(FT_Get_Sfnt_Name_Count) | |
| LOAD_FUNCPTR(FT_Get_Sfnt_Table) | |
| LOAD_FUNCPTR(FT_Init_FreeType) | |
| LOAD_FUNCPTR(FT_Load_Glyph) | |
| LOAD_FUNCPTR(FT_New_Face) | |
| LOAD_FUNCPTR(FT_Set_Charmap) | |
| #undef LOAD_FUNCPTR | |
| error = pFT_Init_FreeType(&library); | |
| if (error != FT_Err_Ok) | |
| { | |
| ERR("%s returned %i\n", "FT_Init_FreeType", error); | |
| wine_dlclose(ft_handle, NULL, 0); | |
| RegCloseKey(hkey); | |
| return FALSE; | |
| } | |
| name_len = sizeof(name_buf); | |
| value_len = sizeof(value_buf); | |
| while (RegEnumValueA(hkey, i++, name_buf, &name_len, NULL, &type, value_buf, | |
| &value_len) == ERROR_SUCCESS) | |
| { | |
| value_buf[sizeof(value_buf) - 1] = '\0'; | |
| if (ReadTrueTypeDir(library, value_buf) == FALSE) | |
| { | |
| RegCloseKey(hkey); | |
| pFT_Done_FreeType(library); | |
| return FALSE; | |
| } | |
| /* initialize lengths for new iteration */ | |
| name_len = sizeof(name_buf); | |
| value_len = sizeof(value_buf); | |
| } | |
| RegCloseKey(hkey); | |
| pFT_Done_FreeType(library); | |
| wine_dlclose(ft_handle, NULL, 0); | |
| ft_handle = NULL; | |
| return TRUE; | |
| sym_not_found: | |
| WINE_MESSAGE( | |
| "Wine cannot find certain functions that it needs inside the FreeType\n" | |
| "font library. To enable Wine to use TrueType fonts please upgrade\n" | |
| "FreeType to at least version 2.0.5.\n" | |
| "http://www.freetype.org\n"); | |
| wine_dlclose(ft_handle, NULL, 0); | |
| ft_handle = NULL; | |
| return TRUE; | |
| } | |
| #endif /* HAVE_FREETYPE */ |