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