- fixme when unexpected children found
[wine] / dlls / wineps / truetype.c
1 /*******************************************************************************
2  *  TrueType font-related functions for Wine PostScript driver.  Currently just
3  *  uses FreeType to read font metrics.
4  *
5  *  Copyright 2001  Ian Pilcher
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  *
21  *  NOTE:  Many of the functions in this file can return either fatal errors
22  *      (memory allocation failure or unexpected FreeType error) or non-fatal
23  *      errors (unusable font file).  Fatal errors are indicated by returning
24  *      FALSE; see individual function descriptions for how they indicate non-
25  *      fatal errors.
26  *
27  */
28 #include "config.h"
29 #include "wine/port.h"
30
31 #ifdef HAVE_FREETYPE
32
33 /*
34  *  These stupid #ifdefs should work for FreeType 2.0.1 and 2.0.2.  Beyond that
35  *  is anybody's guess.
36  */
37
38 #ifdef HAVE_FT2BUILD_H
39 #include <ft2build.h>
40 #endif
41 #ifdef HAVE_FREETYPE_FREETYPE_H
42 #include <freetype/freetype.h>
43 #endif
44 #ifdef HAVE_FREETYPE_FTGLYPH_H
45 #include <freetype/ftglyph.h>
46 #endif
47 #ifdef HAVE_FREETYPE_TTTABLES_H
48 #include <freetype/tttables.h>
49 #endif
50 #ifdef HAVE_FREETYPE_FTSNAMES_H
51 #include <freetype/ftsnames.h>
52 #else
53 # ifdef HAVE_FREETYPE_FTNAMES_H
54 # include <freetype/ftnames.h>
55 # endif
56 #endif
57 #ifdef HAVE_FREETYPE_TTNAMEID_H
58 #include <freetype/ttnameid.h>
59 #endif
60
61 #include <sys/types.h>
62 #include <dirent.h>
63 #include <string.h>
64 #include <stdarg.h>
65 #include <stdio.h>
66 #include <errno.h>
67
68 #include "windef.h"
69 #include "winbase.h"
70 #include "winerror.h"
71 #include "winreg.h"
72 #include "psdrv.h"
73 #include "wine/debug.h"
74
75 WINE_DEFAULT_DEBUG_CHANNEL(psdrv);
76
77 #define REQUIRED_FACE_FLAGS     (   FT_FACE_FLAG_SCALABLE   |   \
78                                     FT_FACE_FLAG_HORIZONTAL |   \
79                                     FT_FACE_FLAG_SFNT       |   \
80                                     FT_FACE_FLAG_GLYPH_NAMES    )
81
82 #define GLYPH_LOAD_FLAGS        (   FT_LOAD_NO_SCALE            |   \
83                                     FT_LOAD_IGNORE_TRANSFORM    |   \
84                                     FT_LOAD_LINEAR_DESIGN           )
85
86 #ifndef SONAME_LIBFREETYPE
87 #define SONAME_LIBFREETYPE "libfreetype.so"
88 #endif
89
90 static void *ft_handle = NULL;
91
92 #define MAKE_FUNCPTR(f) static typeof(f) * p##f = NULL;
93 MAKE_FUNCPTR(FT_Done_Face)
94 MAKE_FUNCPTR(FT_Done_FreeType)
95 MAKE_FUNCPTR(FT_Get_Char_Index)
96 MAKE_FUNCPTR(FT_Get_Glyph_Name)
97 MAKE_FUNCPTR(FT_Get_Sfnt_Name)
98 MAKE_FUNCPTR(FT_Get_Sfnt_Name_Count)
99 MAKE_FUNCPTR(FT_Get_Sfnt_Table)
100 MAKE_FUNCPTR(FT_Init_FreeType)
101 MAKE_FUNCPTR(FT_Load_Glyph)
102 MAKE_FUNCPTR(FT_New_Face)
103 MAKE_FUNCPTR(FT_Set_Charmap)
104 #undef MAKE_FUNCPTR
105
106 /*******************************************************************************
107  *  FindCharMap
108  *
109  *  Finds Windows character map and creates "EncodingScheme" string.  Returns
110  *  FALSE to indicate memory allocation or FreeType error; sets *p_charmap to
111  *  NULL if no Windows encoding is present.
112  *
113  *  Returns Unicode character map if present; otherwise uses the first Windows
114  *  character map found.
115  *
116  */
117 static const LPCSTR encoding_names[7] =
118 {
119     "WindowsSymbol",        /* TT_MS_ID_SYMBOL_CS */
120     "WindowsUnicode",       /* TT_MS_ID_UNICODE_CS */
121     "WindowsShiftJIS",      /* TT_MS_ID_SJIS */
122     "WindowsPRC",           /* TT_MS_ID_GB2312 */
123     "WindowsBig5",          /* TT_MS_ID_BIG_5 */
124     "WindowsWansung",       /* TT_MS_ID_WANSUNG */
125     "WindowsJohab"          /* TT_MS_ID_JOHAB */
126 /*  "WindowsUnknown65535"   is the longest possible (encoding_id is a UShort) */
127 };
128
129 static BOOL FindCharMap(FT_Face face, FT_CharMap *p_charmap, LPSTR *p_sz)
130 {
131     FT_Int      i;
132     FT_Error    error;
133     FT_CharMap  charmap = NULL;
134
135     for (i = 0; i < face->num_charmaps; ++i)
136     {
137         if (face->charmaps[i]->platform_id != TT_PLATFORM_MICROSOFT)
138             continue;
139
140         if (face->charmaps[i]->encoding_id == TT_MS_ID_UNICODE_CS)
141         {
142             charmap = face->charmaps[i];
143             break;
144         }
145
146         if (charmap == NULL)
147             charmap = face->charmaps[i];
148     }
149
150     *p_charmap = charmap;
151
152     if (charmap == NULL)
153     {
154         WARN("No Windows character map found\n");
155         return TRUE;
156     }
157
158     error = pFT_Set_Charmap(face, charmap);
159     if (error != FT_Err_Ok)
160     {
161         ERR("%s returned %i\n", "FT_Set_Charmap", error);
162         return FALSE;
163     }
164
165     *p_sz = HeapAlloc(PSDRV_Heap, 0, sizeof("WindowsUnknown65535"));
166     if (*p_sz == NULL)
167         return FALSE;
168
169     if (charmap->encoding_id < 7)
170         strcpy(*p_sz, encoding_names[charmap->encoding_id]);
171     else
172         sprintf(*p_sz, "%s%u", "WindowsUnknown", charmap->encoding_id);
173
174     return TRUE;
175 }
176
177 /*******************************************************************************
178  *  MSTTStrToSz
179  *
180  *  Converts a string in the TrueType NAME table to a null-terminated ASCII
181  *  character string.  Space for the string is allocated from the driver heap.
182  *  Only handles platform_id = 3 (TT_PLATFORM_MICROSOFT) strings (16-bit, big
183  *  endian).  It also only handles ASCII character codes (< 128).
184  *
185  *  Sets *p_sz to NULL if string cannot be converted; only returns FALSE for
186  *  memory allocation failure.
187  *
188  */
189 static BOOL MSTTStrToSz(const FT_SfntName *name, LPSTR *p_sz)
190 {
191     FT_UShort   i;
192     INT         len;
193     BYTE        *wsz;
194     LPSTR       sz;
195
196     len = name->string_len / 2;                     /* # of 16-bit chars */
197
198     *p_sz = sz = HeapAlloc(PSDRV_Heap, 0, len + 1);
199     if (sz == NULL)
200         return FALSE;
201
202     wsz = (BYTE *)name->string;
203
204     for (i = 0; i < len; ++i, ++sz)
205     {
206         USHORT wc = (wsz[0] << 8) + wsz[1];
207         wsz += 2;
208
209         if (wc > 127)
210         {
211             WARN("Non-ASCII character 0x%.4x\n", wc);
212             HeapFree(PSDRV_Heap, 0, *p_sz);
213             *p_sz = NULL;
214             return TRUE;
215         }
216
217         *sz = (CHAR)wc;
218     }
219
220     *sz = '\0';
221
222     return TRUE;
223 }
224
225 /*******************************************************************************
226  *  FindMSTTString
227  *
228  *  Finds the requested Microsoft platform string in the TrueType NAME table and
229  *  converts it to a null-terminated ASCII string.  Currently looks for U.S.
230  *  English names only.
231  *
232  *  Sets string to NULL if not present or cannot be converted; returns FALSE
233  *  only for memory allocation failure.
234  *
235  */
236 static BOOL FindMSTTString(FT_Face face, FT_CharMap charmap, FT_UShort name_id,
237         LPSTR *p_sz)
238 {
239     FT_UInt         num_strings, string_index;
240     FT_SfntName     name;
241     FT_Error        error;
242
243     num_strings = pFT_Get_Sfnt_Name_Count(face);
244
245     for (string_index = 0; string_index < num_strings; ++string_index)
246     {
247         error = pFT_Get_Sfnt_Name(face, string_index, &name);
248         if (error != FT_Err_Ok)
249         {
250             ERR("%s returned %i\n", "FT_Get_Sfnt_Name", error);
251             return FALSE;
252         }
253
254         /* FIXME - Handle other languages? */
255
256         if (name.platform_id != TT_PLATFORM_MICROSOFT ||
257                 name.language_id != TT_MS_LANGID_ENGLISH_UNITED_STATES)
258             continue;
259
260         if (name.platform_id != charmap->platform_id ||
261                 name.encoding_id != charmap->encoding_id)
262             continue;
263
264         if (name.name_id != name_id)
265             continue;
266
267         return MSTTStrToSz(&name, p_sz);
268     }
269
270     *p_sz = NULL;                   /* didn't find it */
271
272     return TRUE;
273 }
274
275 /*******************************************************************************
276  *  PSUnits
277  *
278  *  Convert TrueType font units (relative to font em square) to PostScript
279  *  units.
280  *
281  */
282 inline static float PSUnits(LONG x, USHORT em_size)
283 {
284     return 1000.0 * (float)x / (float)em_size;
285 }
286
287 /*******************************************************************************
288  *  StartAFM
289  *
290  *  Allocates space for the AFM on the driver heap and reads basic font metrics
291  *  from the HEAD, POST, HHEA, and OS/2 tables.  Returns FALSE for memory
292  *  allocation error; sets *p_afm to NULL if required information is missing.
293  *
294  */
295 static BOOL StartAFM(FT_Face face, AFM **p_afm)
296 {
297     TT_Header       *head;
298     TT_Postscript   *post;
299     TT_OS2          *os2;
300     TT_HoriHeader   *hhea;
301     USHORT          em_size;
302     AFM             *afm;
303
304     head = pFT_Get_Sfnt_Table(face, ft_sfnt_head);
305     post = pFT_Get_Sfnt_Table(face, ft_sfnt_post);
306     os2 = pFT_Get_Sfnt_Table(face, ft_sfnt_os2);
307     hhea = pFT_Get_Sfnt_Table(face, ft_sfnt_hhea);
308
309     if (head == NULL || post == NULL || os2 == NULL || hhea == NULL ||
310             os2->version == 0xffff)                     /* old Macintosh font */
311     {
312         WARN("Required table(s) missing\n");
313         *p_afm = NULL;
314         return TRUE;
315     }
316
317     *p_afm = afm = HeapAlloc(PSDRV_Heap, 0, sizeof(*afm));
318     if (afm == NULL)
319         return FALSE;
320
321     afm->WinMetrics.usUnitsPerEm = em_size = head->Units_Per_EM;
322     afm->WinMetrics.sAscender = hhea->Ascender;
323     afm->WinMetrics.sDescender = hhea->Descender;
324     afm->WinMetrics.sLineGap = hhea->Line_Gap;
325     afm->WinMetrics.sTypoAscender = os2->sTypoAscender;
326     afm->WinMetrics.sTypoDescender = os2->sTypoDescender;
327     afm->WinMetrics.sTypoLineGap = os2->sTypoLineGap;
328     afm->WinMetrics.usWinAscent = os2->usWinAscent;
329     afm->WinMetrics.usWinDescent = os2->usWinDescent;
330     afm->WinMetrics.sAvgCharWidth = os2->xAvgCharWidth;
331
332     afm->Weight = os2->usWeightClass;
333     afm->ItalicAngle = ((float)(post->italicAngle)) / 65536.0;
334     afm->IsFixedPitch = (post-> isFixedPitch == 0) ? FALSE : TRUE;
335     afm->UnderlinePosition = PSUnits(post->underlinePosition, em_size);
336     afm->UnderlineThickness = PSUnits(post->underlineThickness, em_size);
337
338     afm->FontBBox.llx = PSUnits(head->xMin, em_size);
339     afm->FontBBox.lly = PSUnits(head->yMin, em_size);
340     afm->FontBBox.urx = PSUnits(head->xMax, em_size);
341     afm->FontBBox.ury = PSUnits(head->yMax, em_size);
342
343     afm->Ascender = PSUnits(os2->sTypoAscender, em_size);
344     afm->Descender = PSUnits(os2->sTypoDescender, em_size);
345
346     return TRUE;
347 }
348
349 /*******************************************************************************
350  *  ReadCharMetrics
351  *
352  *  Reads metrics for each glyph in a TrueType font.  Returns false for memory
353  *  allocation or FreeType error; sets *p_metrics to NULL for non-fatal error.
354  *
355  */
356 static BOOL ReadCharMetrics(FT_Face face, AFM *afm, AFMMETRICS **p_metrics)
357 {
358     FT_ULong    charcode, index;
359     AFMMETRICS  *metrics;
360     USHORT      em_size = afm->WinMetrics.usUnitsPerEm;
361
362     for (charcode = 0, index = 0; charcode < 65536; ++charcode)
363         if (pFT_Get_Char_Index(face, charcode) != 0)
364             ++index;                                    /* count # of glyphs */
365
366     afm->NumofMetrics = index;
367
368     *p_metrics = metrics = HeapAlloc(PSDRV_Heap, 0, index * sizeof(*metrics));
369     if (metrics == NULL)
370         return FALSE;
371
372     for (charcode = 0, index = 0; charcode < 65536; ++charcode)
373     {
374         FT_UInt     glyph_index = pFT_Get_Char_Index(face, charcode);
375         FT_Error    error;
376         CHAR        buffer[128];                /* for glyph names */
377
378         if (glyph_index == 0)
379             continue;
380
381         error = pFT_Load_Glyph(face, glyph_index, GLYPH_LOAD_FLAGS);
382         if (error != FT_Err_Ok)
383         {
384             ERR("%s returned %i\n", "FT_Load_Glyph", error);
385             goto cleanup;
386         }
387
388         error = pFT_Get_Glyph_Name(face, glyph_index, buffer, sizeof(buffer));
389         if (error != FT_Err_Ok)
390         {
391             ERR("%s returned %i\n", "FT_Get_Glyph_Name", error);
392             goto cleanup;
393         }
394
395         metrics[index].N = PSDRV_GlyphName(buffer);
396         if (metrics[index].N == NULL)
397             goto cleanup;
398
399         metrics[index].C = metrics[index].UV = charcode;
400         metrics[index].WX = PSUnits(face->glyph->metrics.horiAdvance, em_size);
401
402         ++index;
403     }
404
405     if (afm->WinMetrics.sAvgCharWidth == 0)
406         afm->WinMetrics.sAvgCharWidth = PSDRV_CalcAvgCharWidth(afm);
407
408     return TRUE;
409
410     cleanup:
411         HeapFree(PSDRV_Heap, 0, metrics);
412
413     return FALSE;
414 }
415
416 /*******************************************************************************
417  *  BuildTrueTypeAFM
418  *
419  *  Builds the AFM for a TrueType font and adds it to the driver font list.
420  *  Returns FALSE only on an unexpected error (memory allocation failure or
421  *  FreeType error).
422  *
423  */
424 static BOOL BuildTrueTypeAFM(FT_Face face)
425 {
426     AFM         *afm;
427     AFMMETRICS  *metrics;
428     LPSTR       font_name, full_name, family_name, encoding_scheme;
429     FT_CharMap  charmap;
430     BOOL        retval, added;
431
432     retval = StartAFM(face, &afm);
433     if (retval == FALSE || afm == NULL)
434         return retval;
435
436     retval = FindCharMap(face, &charmap, &encoding_scheme);
437     if (retval == FALSE || charmap == NULL)
438         goto cleanup_afm;
439
440     retval = FindMSTTString(face, charmap, TT_NAME_ID_PS_NAME, &font_name);
441     if (retval == FALSE || font_name == NULL)
442         goto cleanup_encoding_scheme;
443
444     retval = FindMSTTString(face, charmap, TT_NAME_ID_FULL_NAME, &full_name);
445     if (retval == FALSE || full_name == NULL)
446         goto cleanup_font_name;
447
448     retval = FindMSTTString(face, charmap, TT_NAME_ID_FONT_FAMILY,
449             &family_name);
450     if (retval == FALSE || family_name == NULL)
451         goto cleanup_full_name;
452
453     retval = ReadCharMetrics(face, afm, &metrics);
454     if (retval == FALSE || metrics == NULL)
455         goto cleanup_family_name;
456
457     afm->EncodingScheme = encoding_scheme; afm->FontName = font_name;
458     afm->FullName = full_name; afm->FamilyName = family_name;
459     afm->Metrics = metrics;
460
461     retval = PSDRV_AddAFMtoList(&PSDRV_AFMFontList, afm, &added);
462     if (retval == FALSE || added == FALSE)
463         goto cleanup_family_name;
464
465     return TRUE;
466
467     /* clean up after fatal or non-fatal errors */
468
469     cleanup_family_name:
470         HeapFree(PSDRV_Heap, 0, family_name);
471     cleanup_full_name:
472         HeapFree(PSDRV_Heap, 0, full_name);
473     cleanup_font_name:
474         HeapFree(PSDRV_Heap, 0, font_name);
475     cleanup_encoding_scheme:
476         HeapFree(PSDRV_Heap, 0, encoding_scheme);
477     cleanup_afm:
478         HeapFree(PSDRV_Heap, 0, afm);
479
480     return retval;
481 }
482
483 /*******************************************************************************
484  *  ReadTrueTypeFile
485  *
486  *  Reads font metrics from TrueType font file.  Only returns FALSE for
487  *  unexpected errors (memory allocation failure or FreeType error).
488  *
489  */
490 static BOOL ReadTrueTypeFile(FT_Library library, LPCSTR filename)
491 {
492     FT_Error        error;
493     FT_Face         face;
494
495     TRACE("%s\n", filename);
496
497     error = pFT_New_Face(library, filename, 0, &face);
498     if (error != FT_Err_Ok)
499     {
500         WARN("FreeType error %i opening %s\n", error, filename);
501         return TRUE;
502     }
503
504     if ((face->face_flags & REQUIRED_FACE_FLAGS) == REQUIRED_FACE_FLAGS)
505     {
506         if (BuildTrueTypeAFM(face) == FALSE)
507         {
508             pFT_Done_Face(face);
509             return FALSE;
510         }
511     }
512     else
513     {
514         WARN("Required information missing from %s\n", filename);
515     }
516
517     error = pFT_Done_Face(face);
518     if (error != FT_Err_Ok)
519     {
520         ERR("%s returned %i\n", "FT_Done_Face", error);
521         return FALSE;
522     }
523
524     return TRUE;
525 }
526
527 /*******************************************************************************
528  *  ReadTrueTypeDir
529  *
530  *  Reads all TrueType font files in a directory.
531  *
532  */
533 static BOOL ReadTrueTypeDir(FT_Library library, LPCSTR dirname)
534 {
535     struct dirent   *dent;
536     DIR             *dir;
537     CHAR            filename[256];
538
539     dir = opendir(dirname);
540     if (dir == NULL)
541     {
542         WARN("'%s' opening %s\n", strerror(errno), dirname);
543         return TRUE;
544     }
545
546     while ((dent = readdir(dir)) != NULL)
547     {
548         CHAR        *file_extension = strrchr(dent->d_name, '.');
549         int         fn_len;
550
551         if (file_extension == NULL || strcasecmp(file_extension, ".ttf") != 0)
552             continue;
553
554         fn_len = snprintf(filename, 256, "%s/%s", dirname, dent->d_name);
555         if (fn_len < 0 || fn_len > sizeof(filename) - 1)
556         {
557             WARN("Path '%s/%s' is too long\n", dirname, dent->d_name);
558             continue;
559         }
560
561         if (ReadTrueTypeFile(library, filename) ==  FALSE)
562         {
563             closedir(dir);
564             return FALSE;
565         }
566     }
567
568     closedir(dir);
569
570     return TRUE;
571 }
572
573 /*******************************************************************************
574  *  PSDRV_GetTrueTypeMetrics
575  *
576  *  Reads font metrics from TrueType font files in directories listed in the
577  *  [TrueType Font Directories] section of the Wine configuration file.
578  *
579  *  If this function fails (returns FALSE), the driver will fail to initialize
580  *  and the driver heap will be destroyed, so it's not necessary to HeapFree
581  *  everything in that event.
582  *
583  */
584 BOOL PSDRV_GetTrueTypeMetrics(void)
585 {
586     CHAR        name_buf[256], value_buf[256];
587     INT         i = 0;
588     FT_Error    error;
589     FT_Library  library;
590     HKEY        hkey;
591     DWORD       type, name_len, value_len;
592
593     if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,
594             "Software\\Wine\\Wine\\Config\\TrueType Font Directories",
595             0, KEY_READ, &hkey) != ERROR_SUCCESS)
596         return TRUE;
597
598
599     ft_handle = wine_dlopen(SONAME_LIBFREETYPE, RTLD_NOW, NULL, 0);
600     if(!ft_handle) {
601         WINE_MESSAGE(
602       "Wine cannot find the FreeType font library.  To enable Wine to\n"
603       "use TrueType fonts please install a version of FreeType greater than\n"
604       "or equal to 2.0.5.\n"
605       "http://www.freetype.org\n");
606         RegCloseKey(hkey);
607         return TRUE;
608     }
609
610 #define LOAD_FUNCPTR(f) if((p##f = wine_dlsym(ft_handle, #f, NULL, 0)) == NULL) goto sym_not_found;
611     LOAD_FUNCPTR(FT_Done_Face)
612     LOAD_FUNCPTR(FT_Done_FreeType)
613     LOAD_FUNCPTR(FT_Get_Char_Index)
614     LOAD_FUNCPTR(FT_Get_Glyph_Name)
615     LOAD_FUNCPTR(FT_Get_Sfnt_Name)
616     LOAD_FUNCPTR(FT_Get_Sfnt_Name_Count)
617     LOAD_FUNCPTR(FT_Get_Sfnt_Table)
618     LOAD_FUNCPTR(FT_Init_FreeType)
619     LOAD_FUNCPTR(FT_Load_Glyph)
620     LOAD_FUNCPTR(FT_New_Face)
621     LOAD_FUNCPTR(FT_Set_Charmap)
622 #undef LOAD_FUNCPTR
623
624     error = pFT_Init_FreeType(&library);
625     if (error != FT_Err_Ok)
626     {
627         ERR("%s returned %i\n", "FT_Init_FreeType", error);
628         wine_dlclose(ft_handle, NULL, 0);
629         RegCloseKey(hkey);
630         return FALSE;
631     }
632
633     name_len = sizeof(name_buf);
634     value_len = sizeof(value_buf);
635
636     while (RegEnumValueA(hkey, i++, name_buf, &name_len, NULL, &type, value_buf,
637             &value_len) == ERROR_SUCCESS)
638     {
639         value_buf[sizeof(value_buf) - 1] = '\0';
640
641         if (ReadTrueTypeDir(library, value_buf) == FALSE)
642         {
643             RegCloseKey(hkey);
644             pFT_Done_FreeType(library);
645             return FALSE;
646         }
647
648         /* initialize lengths for new iteration */
649
650         name_len = sizeof(name_buf);
651         value_len = sizeof(value_buf);
652     }
653
654     RegCloseKey(hkey);
655     pFT_Done_FreeType(library);
656     wine_dlclose(ft_handle, NULL, 0);
657     ft_handle = NULL;
658     return TRUE;
659
660 sym_not_found:
661     WINE_MESSAGE(
662       "Wine cannot find certain functions that it needs inside the FreeType\n"
663       "font library.  To enable Wine to use TrueType fonts please upgrade\n"
664       "FreeType to at least version 2.0.5.\n"
665       "http://www.freetype.org\n");
666     RegCloseKey(hkey);
667     wine_dlclose(ft_handle, NULL, 0);
668     ft_handle = NULL;
669     return TRUE;
670 }
671
672 #endif  /* HAVE_FREETYPE */