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