2009-11-13 Jeffrey Stedfast <fejj@novell.com>
[moon.git] / src / fontmanager.cpp
blobf6fc55a36fe05d411001e4e2f530025538e3b303
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * fontmanager.cpp:
5 * Contact:
6 * Moonlight List (moonlight-list@lists.ximian.com)
8 * Copyright 2009 Novell, Inc. (http://www.novell.com)
10 * See the LICENSE file included with the distribution for details.
13 #include <config.h>
15 #include <glib/gstdio.h>
16 #include <fcntl.h>
17 #include <errno.h>
18 #include <ctype.h>
20 #include "fontmanager.h"
21 #include "zip/unzip.h"
22 #include "debug.h"
23 #include "utils.h"
24 #include "list.h"
26 #include <fontconfig/fontconfig.h>
27 #include <fontconfig/fcfreetype.h>
28 #include FT_TRUETYPE_TABLES_H
29 #include FT_OUTLINE_H
30 #include FT_SYSTEM_H
31 #include FT_GLYPH_H
35 // OpenType's OS/2 fsSelection Table:
37 // http://www.microsoft.com/typography/otspec/os2.htm#fss
39 enum fsSelection {
40 fsSelectionItalic = (1 << 0),
41 fsSelectionUnderscore = (1 << 1),
42 fsSelectionNegative = (1 << 2),
43 fsSelectionOutlined = (1 << 3),
44 fsSelectionStrikeout = (1 << 4),
45 fsSelectionBold = (1 << 5),
46 fsSelectionRegular = (1 << 6),
47 fsSelectionUseTypoMetrics = (1 << 7),
48 fsSelectionWWS = (1 << 8),
49 fsSelectionOblique = (1 << 9),
52 #define FONT_FACE_SIZE 41.0
54 #define DOUBLE_TO_26_6(d) ((FT_F26Dot6)((d) * 64.0))
55 #define DOUBLE_FROM_26_6(t) ((double)(t) / 64.0)
56 #define DOUBLE_TO_16_16(d) ((FT_Fixed)((d) * 65536.0))
57 #define DOUBLE_FROM_16_16(t) ((double)(t) / 65536.0)
59 #define EMBOLDEN_STRENGTH 0.75
60 #define EMBOLDEN_STRENGTH_26_6 DOUBLE_TO_26_6 (EMBOLDEN_STRENGTH)
61 #define EMBOLDEN_STRENGTH_16_16 DOUBLE_TO_16_16 (EMBOLDEN_STRENGTH)
63 #define ITALIC_SLANT -17.5
64 #define ITALIC_SLANT_RADIANS (ITALIC_SLANT * M_PI / 180.0)
66 static const FT_Matrix italicize = {
67 DOUBLE_TO_16_16 (1.0), DOUBLE_TO_16_16 (tan (ITALIC_SLANT_RADIANS)),
68 DOUBLE_TO_16_16 (0.0), DOUBLE_TO_16_16 (1.0)
71 #define LOAD_FLAGS (FT_LOAD_NO_BITMAP | /*FT_LOAD_NO_HINTING | FT_LOAD_NO_AUTOHINT |*/ FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH | FT_LOAD_TARGET_NORMAL)
76 // Silverlight -> FontConfig enumeration conversion utilities
79 #ifndef FC_WEIGHT_EXTRABLACK
80 #define FC_WEIGHT_EXTRABLACK 215
81 #endif
82 #ifndef FC_WEIGHT_ULTRABLACK
83 #define FC_WEIGHT_ULTRABLACK FC_WEIGHT_EXTRABLACK
84 #endif
86 // Silverlight accepts negative values ]0,-475[ as bold and everything over 1023 as normal
87 #define FONT_LOWER_BOLD_LIMIT -475
88 #define FONT_UPPER_BOLD_LIMIT 1024
90 bool
91 FontWeightIsBold (FontWeights weight)
93 if (weight > FONT_LOWER_BOLD_LIMIT)
94 return weight < 0 || (weight >= FontWeightsSemiBold && weight < FONT_UPPER_BOLD_LIMIT);
96 return false;
99 static int
100 fc_weight (FontWeights weight)
102 if ((weight < 0) && (weight > FONT_LOWER_BOLD_LIMIT))
103 return FC_WEIGHT_BLACK;
104 else if (weight < (FontWeightsThin + FontWeightsLight) / 2)
105 return FC_WEIGHT_ULTRALIGHT;
106 else if (weight < (FontWeightsLight + FontWeightsNormal) / 2)
107 return FC_WEIGHT_LIGHT;
108 else if (weight < (FontWeightsNormal + FontWeightsMedium) / 2)
109 return FC_WEIGHT_NORMAL;
110 else if (weight < (FontWeightsMedium + FontWeightsSemiBold) / 2)
111 return FC_WEIGHT_MEDIUM;
112 else if (weight < (FontWeightsSemiBold + FontWeightsBold) / 2)
113 return FC_WEIGHT_SEMIBOLD;
114 else if (weight < (FontWeightsBold + FontWeightsExtraBold) / 2)
115 return FC_WEIGHT_BOLD;
116 else if (weight < (FontWeightsExtraBold + FontWeightsBlack) / 2)
117 return FC_WEIGHT_ULTRABOLD;
118 else if (weight < FONT_UPPER_BOLD_LIMIT)
119 return FC_WEIGHT_BLACK;
120 else
121 return FC_WEIGHT_NORMAL;
124 static int
125 fc_width (FontStretches stretch)
127 switch (stretch) {
128 case FontStretchesUltraCondensed:
129 return FC_WIDTH_ULTRACONDENSED;
130 case FontStretchesExtraCondensed:
131 return FC_WIDTH_EXTRACONDENSED;
132 case FontStretchesCondensed:
133 return FC_WIDTH_CONDENSED;
134 case FontStretchesSemiCondensed:
135 return FC_WIDTH_SEMICONDENSED;
136 case FontStretchesNormal:
137 return FC_WIDTH_NORMAL;
138 #if 0
139 case FontStretchesMedium:
140 return FC_WIDTH_NORMAL;
141 #endif
142 case FontStretchesSemiExpanded:
143 return FC_WIDTH_SEMIEXPANDED;
144 case FontStretchesExpanded:
145 return FC_WIDTH_EXPANDED;
146 case FontStretchesExtraExpanded:
147 return FC_WIDTH_EXTRAEXPANDED;
148 case FontStretchesUltraExpanded:
149 return FC_WIDTH_ULTRAEXPANDED;
150 default:
151 return FC_WIDTH_NORMAL;
155 static int
156 fc_slant (FontStyles style)
158 switch (style) {
159 case FontStylesNormal:
160 return FC_SLANT_ROMAN;
161 // technically Olbique does not exists in SL 1.0 or 2.0 (it's in WPF) but the parser allows it
162 case FontStylesOblique:
163 return FC_SLANT_OBLIQUE;
164 case FontStylesItalic:
165 // Silverlight defaults bad values to Italic
166 default:
167 return FC_SLANT_ITALIC;
173 // Font-style parser utils
176 #define Width (1 << 0)
177 #define Weight (1 << 1)
178 #define Slant (1 << 2)
180 struct FontStyleInfo {
181 char *family_name;
182 FontStretches width;
183 FontWeights weight;
184 FontStyles slant;
185 int set;
188 static struct {
189 const char *name;
190 size_t len;
191 int type;
192 int value;
193 } style_hints[] = {
194 // widths
195 { "Ultra-Condensed", 15, Width, FontStretchesUltraCondensed },
196 { "Extra-Condensed", 15, Width, FontStretchesExtraCondensed },
197 { "Semi-Condensed", 14, Width, FontStretchesSemiCondensed },
198 { "UltraCondensed", 14, Width, FontStretchesUltraCondensed },
199 { "ExtraCondensed", 14, Width, FontStretchesExtraCondensed },
200 { "SemiCondensed", 13, Width, FontStretchesSemiCondensed },
201 { "Condensed", 9, Width, FontStretchesCondensed },
202 { "Cond", 4, Width, FontStretchesCondensed },
203 { "Ultra-Expanded", 14, Width, FontStretchesUltraExpanded },
204 { "Extra-Expanded", 14, Width, FontStretchesExtraExpanded },
205 { "Semi-Expanded", 13, Width, FontStretchesSemiExpanded },
206 { "UltraExpanded", 13, Width, FontStretchesUltraExpanded },
207 { "ExtraExpanded", 13, Width, FontStretchesExtraExpanded },
208 { "SemiExpanded", 12, Width, FontStretchesSemiExpanded },
209 { "Expanded", 8, Width, FontStretchesExpanded },
211 // weights
212 { "Thin", 4, Weight, FontWeightsThin },
213 { "Ultra-Light", 11, Weight, FontWeightsExtraLight },
214 { "Extra-Light", 11, Weight, FontWeightsExtraLight },
215 { "UltraLight", 10, Weight, FontWeightsExtraLight },
216 { "ExtraLight", 10, Weight, FontWeightsExtraLight },
217 { "Light", 5, Weight, FontWeightsLight },
218 { "Book", 4, Weight, FontWeightsNormal },
219 { "Medium", 6, Weight, FontWeightsMedium },
220 { "Demi-Bold", 9, Weight, FontWeightsSemiBold },
221 { "Semi-Bold", 9, Weight, FontWeightsSemiBold },
222 { "DemiBold", 8, Weight, FontWeightsSemiBold },
223 { "SemiBold", 8, Weight, FontWeightsSemiBold },
224 { "Bold", 4, Weight, FontWeightsBold },
225 { "Extra-Bold", 10, Weight, FontWeightsExtraBold },
226 { "Ultra-Bold", 10, Weight, FontWeightsExtraBold },
227 { "ExtraBold", 9, Weight, FontWeightsExtraBold },
228 { "UltraBold", 9, Weight, FontWeightsExtraBold },
229 { "Black", 5, Weight, FontWeightsBlack },
230 { "Heavy", 5, Weight, FontWeightsBlack },
231 { "Extra-Black", 11, Weight, FontWeightsExtraBlack },
232 { "Ultra-Black", 11, Weight, FontWeightsExtraBlack },
233 { "ExtraBlack", 10, Weight, FontWeightsExtraBlack },
234 { "UltraBlack", 10, Weight, FontWeightsExtraBlack },
236 // slants
237 { "Oblique", 7, Slant, FontStylesOblique },
238 { "Italic", 6, Slant, FontStylesItalic },
239 { "Kursiv", 6, Slant, FontStylesItalic },
241 // changes nothing
242 { "Regular", 7, 0, 0 },
243 { "W3", 2, 0, 0 }, // as in Hiragino Mincho Pro W3
246 static void
247 style_info_parse (const char *style, FontStyleInfo *info, bool family)
249 register const char *inptr = style;
250 const char *first_hint = NULL;
251 const char *token;
252 guint tokens = 0;
253 size_t len;
254 guint i;
256 if (!style)
257 return;
259 while (*inptr) {
260 while (*inptr && isspace ((int) ((unsigned char) *inptr)))
261 inptr++;
263 if (*inptr == '\0')
264 break;
266 token = inptr;
267 while (*inptr && !isspace ((int) ((unsigned char) *inptr)))
268 inptr++;
270 tokens++;
272 if (family && tokens == 1) {
273 // if parsing the family_name, first token must not be interpreted as a style hint
274 continue;
277 len = (size_t) (inptr - token);
278 for (i = 0; i < G_N_ELEMENTS (style_hints); i++) {
279 if (style_hints[i].len == len && !strncmp (style_hints[i].name, token, len)) {
280 switch (style_hints[i].type) {
281 case Width:
282 info->width = (FontStretches) style_hints[i].value;
283 info->set |= Width;
284 break;
285 case Weight:
286 info->weight = (FontWeights) style_hints[i].value;
287 info->set |= Weight;
288 break;
289 case Slant:
290 info->slant = (FontStyles) style_hints[i].value;
291 info->set |= Slant;
292 break;
295 if (!first_hint)
296 first_hint = token;
297 break;
301 if (family && i == G_N_ELEMENTS (style_hints)) {
302 // if we come across an unknown style hint when
303 // parsing the family_name, assume that any previously
304 // found style hints were not actually style hints,
305 // but instead just part of the family name.
306 info->width = FontStretchesNormal;
307 info->weight = FontWeightsNormal;
308 info->slant = FontStylesNormal;
309 info->set = 0;
311 first_hint = NULL;
315 if (family) {
316 if (first_hint)
317 info->family_name = g_strndup (style, first_hint - style);
318 else
319 info->family_name = g_strdup (style);
321 g_strstrip (info->family_name);
325 #ifdef DEBUG
326 static const char *
327 style_info_to_string (FontStretches stretch, FontWeights weight, FontStyles style)
329 static char namebuf[256];
330 guint i = 0;
331 char *p;
333 namebuf[0] = '\0';
334 p = namebuf;
336 if (stretch != FontStretchesNormal) {
337 while (style_hints[i].type == Width) {
338 if (style_hints[i].value == stretch) {
339 p = g_stpcpy (p, style_hints[i].name);
340 break;
343 i++;
347 if (weight != FontWeightsNormal) {
348 while (style_hints[i].type != Weight)
349 i++;
351 while (style_hints[i].type == Weight) {
352 if (style_hints[i].value == weight) {
353 if (p != namebuf)
354 *p++ = ' ';
356 p = g_stpcpy (p, style_hints[i].name);
357 break;
360 i++;
364 if (style != FontStylesNormal) {
365 while (style_hints[i].type != Slant)
366 i++;
368 while (i < G_N_ELEMENTS (style_hints)) {
369 if (style_hints[i].value == style) {
370 if (p != namebuf)
371 *p++ = ' ';
373 p = g_stpcpy (p, style_hints[i].name);
374 break;
377 i++;
381 return namebuf;
383 #endif
385 static bool
386 is_odttf (const char *name)
388 size_t len = strlen (name);
390 if (len > 6 && !g_ascii_strcasecmp (name + len - 6, ".odttf"))
391 return true;
393 return false;
398 // FontStream
401 struct FontStream {
402 bool obfuscated;
403 char guid[16];
404 FILE *fp;
407 static const char *
408 path_get_basename (const char *path)
410 const char *name;
412 if (!(name = strrchr (path, '/')))
413 return path;
415 return name + 1;
418 static bool
419 decode_guid (const char *in, char *guid)
421 const char *inptr = in;
422 int i = 16;
424 while (i > 0 && *inptr && *inptr != '.') {
425 if (*inptr == '-')
426 inptr++;
428 i--;
430 if (*inptr >= '0' && *inptr <= '9')
431 guid[i] = (*inptr - '0') * 16;
432 else if (*inptr >= 'a' && *inptr <= 'f')
433 guid[i] = ((*inptr - 'a') + 10) * 16;
434 else if (*inptr >= 'A' && *inptr <= 'F')
435 guid[i] = ((*inptr - 'A') + 10) * 16;
436 else
437 return false;
439 inptr++;
441 if (*inptr >= '0' && *inptr <= '9')
442 guid[i] += (*inptr - '0');
443 else if (*inptr >= 'a' && *inptr <= 'f')
444 guid[i] += ((*inptr - 'a') + 10);
445 else if (*inptr >= 'A' && *inptr <= 'F')
446 guid[i] += ((*inptr - 'A') + 10);
447 else
448 return false;
450 inptr++;
453 if (i > 0)
454 return false;
456 return true;
459 static bool
460 font_stream_set_guid (FT_Stream stream, const char *guid)
462 FontStream *fs = (FontStream *) stream->descriptor.pointer;
464 if (guid) {
465 fs->obfuscated = decode_guid (guid, fs->guid);
466 } else {
467 fs->obfuscated = false;
470 return fs->obfuscated;
473 static unsigned long
474 font_stream_read (FT_Stream stream, unsigned long offset, unsigned char *buffer, unsigned long count)
476 FontStream *fs = (FontStream *) stream->descriptor.pointer;
477 size_t nread;
479 if (fseek (fs->fp, (long) offset, SEEK_SET) == -1)
480 return 0;
482 if (count == 0 || buffer == NULL)
483 return 0;
485 nread = fread (buffer, 1, count, fs->fp);
487 if (fs->obfuscated && offset < 32 && nread > 0) {
488 /* obfuscated font... need to deobfuscate */
489 unsigned long i = offset;
490 unsigned long j = 0;
492 for ( ; i < 32 && j < nread; i++, j++)
493 buffer[j] = buffer[j] ^ fs->guid[i % 16];
496 return nread;
499 static void
500 font_stream_reset (FT_Stream stream)
502 FontStream *fs = (FontStream *) stream->descriptor.pointer;
504 rewind (fs->fp);
505 stream->pos = 0;
508 static void
509 font_stream_close (FT_Stream stream)
511 // no-op
514 static FT_Stream
515 font_stream_new (const char *filename, const char *guid)
517 FT_Stream stream;
518 FontStream *fs;
519 FILE *fp;
521 if (!(fp = fopen (filename, "r")))
522 return NULL;
524 fs = (FontStream *) g_malloc (sizeof (FontStream));
525 fs->obfuscated = false;
526 fs->fp = fp;
528 stream = (FT_Stream) g_malloc0 (sizeof (FT_StreamRec));
529 stream->close = font_stream_close;
530 stream->read = font_stream_read;
531 stream->descriptor.pointer = fs;
533 fseek (fp, 0, SEEK_END);
534 stream->size = ftell (fp);
535 fseek (fp, 0, SEEK_SET);
537 font_stream_set_guid (stream, guid);
539 return stream;
542 static void
543 font_stream_destroy (FT_Stream stream)
545 FontStream *fs = (FontStream *) stream->descriptor.pointer;
547 g_free (stream);
548 fclose (fs->fp);
549 g_free (fs);
554 // FaceInfo
557 struct FontFile : public List::Node {
558 GPtrArray *faces;
559 char *path, *guid;
561 FontFile (const char *path, const char *guid);
562 ~FontFile ();
565 struct FaceInfo {
566 FontStyleInfo style;
567 char *family_name;
568 FontFile *file;
569 int index;
571 FaceInfo (FontFile *file, FT_Face face, int index);
572 ~FaceInfo ();
575 FaceInfo::FaceInfo (FontFile *file, FT_Face face, int index)
577 LOG_FONT (stderr, " * indexing %s[%d]: family=\"%s\"; style=\"%s\"\n",
578 path_get_basename (file->path), index, face->family_name, face->style_name);
580 style.width = FontStretchesNormal;
581 style.weight = FontWeightsNormal;
582 style.slant = FontStylesNormal;
583 style.family_name = NULL;
584 style.set = 0;
586 // extract whatever little style info we can from the family name
587 style_info_parse (face->family_name, &style, true);
589 // style info parsed from style_name overrides anything we got from family_name
590 style_info_parse (face->style_name, &style, false);
592 family_name = style.family_name;
594 LOG_FONT (stderr, " * indexed as %s; %s\n", family_name,
595 style_info_to_string (style.width, style.weight, style.slant));
597 this->index = index;
598 this->file = file;
601 FaceInfo::~FaceInfo ()
603 g_free (family_name);
608 // FontFile
611 FontFile::FontFile (const char *path, const char *guid)
613 this->path = g_strdup (path);
614 this->guid = g_strdup (guid);
615 faces = NULL;
618 FontFile::~FontFile ()
620 if (faces != NULL) {
621 FaceInfo *face;
623 for (guint i = 0; i < faces->len; i++) {
624 face = (FaceInfo *) faces->pdata[i];
625 delete face;
628 g_ptr_array_free (faces, true);
631 g_free (path);
632 g_free (guid);
637 // FontIndex
640 struct FontIndex {
641 List *fonts;
642 char *name;
643 char *path;
645 FontIndex (const char *name);
646 ~FontIndex ();
648 void CacheFontInfo (FT_Library libft2, const char *filename, FT_Stream stream, FT_Face face, const char *guid);
651 FontIndex::FontIndex (const char *name)
653 this->name = g_strdup (name);
654 fonts = new List ();
655 path = NULL;
658 FontIndex::~FontIndex ()
660 g_free (name);
661 g_free (path);
662 delete fonts;
665 void
666 FontIndex::CacheFontInfo (FT_Library libft2, const char *filename, FT_Stream stream, FT_Face face, const char *guid)
668 int i = 0, nfaces = face->num_faces;
669 FT_Open_Args args;
670 FontFile *file;
671 FaceInfo *fi;
673 LOG_FONT (stderr, " * caching font info for `%s'...\n", filename);
675 file = new FontFile (filename, guid);
676 file->faces = g_ptr_array_new ();
678 do {
679 args.flags = FT_OPEN_STREAM;
680 args.stream = stream;
682 if (i > 0 && FT_Open_Face (libft2, &args, i, &face) != 0)
683 break;
685 fi = new FaceInfo (file, face, i);
686 g_ptr_array_add (file->faces, fi);
688 FT_Done_Face (face);
690 font_stream_reset (stream);
692 i++;
693 } while (i < nfaces);
695 fonts->Append (file);
698 static void
699 font_index_destroy (gpointer user_data)
701 delete ((FontIndex *) user_data);
706 // FontFace
709 FontFace::FontFace (FontManager *manager, FT_Face face, char *key)
711 FT_Set_Pixel_Sizes (face, 0, (int) FONT_FACE_SIZE);
712 this->cur_size = FONT_FACE_SIZE;
713 this->manager = manager;
714 this->ref_count = 1;
715 this->face = face;
716 this->key = key;
718 g_hash_table_insert (manager->faces, key, this);
721 FontFace::~FontFace ()
723 FT_Stream stream;
725 g_hash_table_steal (manager->faces, key);
727 stream = face->stream;
728 FT_Done_Face (face);
729 font_stream_destroy (stream);
730 g_free (key);
733 void
734 FontFace::ref ()
736 ref_count++;
739 void
740 FontFace::unref ()
742 ref_count--;
744 if (ref_count == 0)
745 delete this;
748 const char *
749 FontFace::GetFamilyName ()
751 return face->family_name;
754 const char *
755 FontFace::GetStyleName ()
757 return face->style_name;
760 bool
761 FontFace::IsScalable ()
763 return FT_IS_SCALABLE (face);
766 bool
767 FontFace::IsItalic ()
769 return (face->style_flags & FT_STYLE_FLAG_ITALIC);
772 bool
773 FontFace::IsBold ()
775 return (face->style_flags & FT_STYLE_FLAG_BOLD);
778 gunichar
779 FontFace::GetCharFromIndex (guint32 index)
781 gunichar unichar;
782 guint32 idx;
784 if (index == 0)
785 return 0;
787 unichar = FT_Get_First_Char (face, &idx);
788 while (idx != index && idx != 0)
789 unichar = FT_Get_Next_Char (face, unichar, &idx);
791 if (idx == 0)
792 unichar = 0;
794 return unichar;
797 guint32
798 FontFace::GetCharIndex (gunichar unichar)
800 return FcFreeTypeCharIndex (face, unichar);
803 bool
804 FontFace::HasChar (gunichar unichar)
806 return FcFreeTypeCharIndex (face, unichar) != 0;
809 void
810 FontFace::GetExtents (double size, FontFaceExtents *extents)
812 double scale = size / face->units_per_EM;
814 if (FT_IS_SFNT (face)) {
815 TT_HoriHeader *hhea = (TT_HoriHeader *) FT_Get_Sfnt_Table (face, ft_sfnt_hhea);
816 TT_OS2 *os2 = (TT_OS2 *) FT_Get_Sfnt_Table (face, ft_sfnt_os2);
817 int height, ascender, descender;
819 if (os2 && (os2->fsSelection & fsSelectionUseTypoMetrics)) {
820 // Use the typographic Ascender, Descender, and LineGap values for everything.
821 height = os2->sTypoAscender - os2->sTypoDescender + os2->sTypoLineGap;
822 descender = -os2->sTypoDescender;
823 ascender = os2->sTypoAscender;
824 } else {
825 // Calculate the LineSpacing for both the hhea table and the OS/2 table.
826 int hhea_height = hhea->Ascender + abs (hhea->Descender) + hhea->Line_Gap;
827 int os2_height = os2 ? (os2->usWinAscent + os2->usWinDescent) : 0;
829 // The LineSpacing is the maximum of the two sumations.
830 height = MAX (hhea_height, os2_height);
832 // If the OS/2 table exists, use usWinAscent as the
833 // ascender. Otherwise use hhea's Ascender value.
834 ascender = os2 ? os2->usWinAscent : hhea->Ascender;
836 // The Descender becomes the difference between the
837 // LineSpacing and the Ascender.
838 descender = height - ascender;
841 extents->descent = -descender * scale;
842 extents->ascent = ascender * scale;
843 extents->height = height * scale;
844 } else {
845 // Fall back to the default FreeType2 values.
846 extents->descent = face->descender * scale;
847 extents->ascent = face->ascender * scale;
848 extents->height = face->height * scale;
851 extents->underline_thickness = face->underline_thickness * scale;
852 extents->underline_position = -face->underline_position * scale;
853 extents->underline_position += ((extents->underline_thickness + 1) / 2.0);
855 if (extents->underline_thickness < 1.0)
856 extents->underline_thickness = 1.0;
859 double
860 FontFace::Kerning (double size, guint32 left, guint32 right)
862 FT_Vector kerning;
864 if (!FT_HAS_KERNING (face) || left == 0 || right == 0)
865 return 0.0;
867 if (size <= FONT_FACE_SIZE) {
868 if (cur_size != FONT_FACE_SIZE) {
869 FT_Set_Pixel_Sizes (face, 0, (int) FONT_FACE_SIZE);
870 cur_size = FONT_FACE_SIZE;
873 FT_Get_Kerning (face, left, right, FT_KERNING_DEFAULT, &kerning);
875 return (kerning.x * size) / (FONT_FACE_SIZE * 64.0);
876 } else {
877 if (cur_size != size) {
878 FT_Set_Pixel_Sizes (face, 0, (int) size);
879 cur_size = size;
882 FT_Get_Kerning (face, left, right, FT_KERNING_DEFAULT, &kerning);
884 return kerning.x / 64.0;
888 static int
889 font_move_to (FT_Vector *to, void *user_data)
891 moon_path *path = (moon_path *) user_data;
892 double x, y;
894 x = DOUBLE_FROM_26_6 (to->x);
895 y = DOUBLE_FROM_26_6 (to->y);
897 moon_move_to (path, x, y);
899 return 0;
902 static int
903 font_line_to (FT_Vector *to, void *user_data)
905 moon_path *path = (moon_path *) user_data;
906 double x, y;
908 x = DOUBLE_FROM_26_6 (to->x);
909 y = DOUBLE_FROM_26_6 (to->y);
911 moon_line_to (path, x, y);
913 return 0;
916 static int
917 font_conic_to (FT_Vector *control, FT_Vector *to, void *user_data)
919 moon_path *path = (moon_path *) user_data;
920 double x3, y3;
921 double x, y;
923 x = DOUBLE_FROM_26_6 (control->x);
924 y = DOUBLE_FROM_26_6 (control->y);
926 x3 = DOUBLE_FROM_26_6 (to->x);
927 y3 = DOUBLE_FROM_26_6 (to->y);
929 moon_quad_curve_to (path, x, y, x3, y3);
931 return 0;
934 static int
935 font_cubic_to (FT_Vector *control1, FT_Vector *control2, FT_Vector *to, void *user_data)
937 moon_path *path = (moon_path *) user_data;
938 double x0, y0;
939 double x1, y1;
940 double x2, y2;
942 x0 = DOUBLE_FROM_26_6 (control1->x);
943 y0 = DOUBLE_FROM_26_6 (control1->y);
945 x1 = DOUBLE_FROM_26_6 (control2->x);
946 y1 = DOUBLE_FROM_26_6 (control2->y);
948 x2 = DOUBLE_FROM_26_6 (to->x);
949 y2 = DOUBLE_FROM_26_6 (to->y);
951 moon_curve_to (path, x0, y0, x1, y1, x2, y2);
953 return 0;
956 static const FT_Outline_Funcs outline_funcs = {
957 (FT_Outline_MoveToFunc) font_move_to,
958 (FT_Outline_LineToFunc) font_line_to,
959 (FT_Outline_ConicToFunc) font_conic_to,
960 (FT_Outline_CubicToFunc) font_cubic_to,
961 0, /* shift */
962 0, /* delta */
965 bool
966 FontFace::LoadGlyph (double size, GlyphInfo *glyph, StyleSimulations simulate)
968 FT_Glyph_Metrics *metrics;
969 FT_Fixed hori_adj = 0;
970 FT_Pos bbox_adj = 0;
971 FT_Matrix matrix;
972 double scale;
974 if (!face)
975 return false;
977 if (size <= FONT_FACE_SIZE) {
978 if (cur_size != FONT_FACE_SIZE) {
979 FT_Set_Pixel_Sizes (face, 0, (int) FONT_FACE_SIZE);
980 cur_size = FONT_FACE_SIZE;
983 scale = size / FONT_FACE_SIZE;
984 } else {
985 if (cur_size != size) {
986 FT_Set_Pixel_Sizes (face, 0, (int) size);
987 cur_size = size;
990 scale = 1.0;
993 if (FT_Load_Glyph (face, glyph->index, LOAD_FLAGS) != 0)
994 return false;
996 if (FT_Render_Glyph (face->glyph, FT_RENDER_MODE_NORMAL) != 0)
997 return false;
999 // invert the glyph over the y-axis and scale
1000 matrix.xx = DOUBLE_TO_16_16 (scale);
1001 matrix.xy = 0;
1002 matrix.yx = 0;
1003 matrix.yy = -DOUBLE_TO_16_16 (scale);
1005 if ((simulate & StyleSimulationsBold) != 0) {
1006 FT_Outline_Embolden (&face->glyph->outline, EMBOLDEN_STRENGTH_26_6);
1007 hori_adj = EMBOLDEN_STRENGTH_16_16;
1008 bbox_adj = EMBOLDEN_STRENGTH_26_6;
1011 if ((simulate & StyleSimulationsItalic) != 0)
1012 FT_Matrix_Multiply (&italicize, &matrix);
1014 glyph->path = moon_path_new (8);
1015 FT_Outline_Transform (&face->glyph->outline, &matrix);
1016 FT_Outline_Decompose (&face->glyph->outline, &outline_funcs, glyph->path);
1018 metrics = &face->glyph->metrics;
1020 glyph->metrics.horiBearingX = DOUBLE_FROM_26_6 (metrics->horiBearingX) * scale;
1021 //glyph->metrics.horiBearingY = DOUBLE_FROM_26_6 (metrics->horiBearingY) * scale;
1022 // always prefer linearHoriAdvance over horiAdvance since the later is rounded to an integer
1023 glyph->metrics.horiAdvance = DOUBLE_FROM_16_16 (face->glyph->linearHoriAdvance + hori_adj) * scale;
1024 //glyph->metrics.height = DOUBLE_FROM_26_6 (metrics->height + bbox_adj) * scale;
1025 //glyph->metrics.width = DOUBLE_FROM_26_6 (metrics->width + bbox_adj) * scale;
1027 return true;
1030 static void
1031 font_face_destroy (gpointer data)
1033 FontFace *face = (FontFace *) data;
1035 delete face;
1040 // FontManager
1043 FontManager::FontManager ()
1045 FcPattern *pattern;
1047 resources = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, font_index_destroy);
1048 faces = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, font_face_destroy);
1049 system_faces = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
1051 FT_Init_FreeType (&libft2);
1053 pattern = FcPatternBuild (NULL, FC_FAMILY, FcTypeString, "Sans",
1054 FC_SIZE, FcTypeDouble, 10.0, NULL);
1056 if (FcPatternGetDouble (pattern, FC_DPI, 0, &dpi) != FcResultMatch)
1057 dpi = 72.0;
1059 FcPatternDestroy (pattern);
1060 root = NULL;
1063 FontManager::~FontManager ()
1065 g_hash_table_destroy (system_faces);
1066 g_hash_table_destroy (resources);
1067 g_hash_table_destroy (faces);
1068 FT_Done_FreeType (libft2);
1070 if (root) {
1071 RemoveDir (root);
1072 g_free (root);
1076 static bool
1077 IndexFontSubdirectory (FT_Library libft2, const char *name, GString *path, FontIndex **out)
1079 FontIndex *fontdir = *out;
1080 const gchar *dirname;
1081 FT_Open_Args args;
1082 FT_Stream stream;
1083 bool obfuscated;
1084 struct stat st;
1085 FT_Face face;
1086 size_t len;
1087 GDir *dir;
1089 if (!(dir = g_dir_open (path->str, 0, NULL)))
1090 return fontdir != NULL;
1092 LOG_FONT (stderr, " * indexing font directory `%s'...\n", path->str);
1094 g_string_append_c (path, G_DIR_SEPARATOR);
1095 len = path->len;
1097 while ((dirname = g_dir_read_name (dir))) {
1098 if (!strcmp (dirname, "..") ||
1099 !strcmp (dirname, "."))
1100 continue;
1102 g_string_append (path, dirname);
1104 if (g_stat (path->str, &st) == -1)
1105 goto next;
1107 if (S_ISDIR (st.st_mode)) {
1108 IndexFontSubdirectory (libft2, name, path, &fontdir);
1109 goto next;
1112 if (!(stream = font_stream_new (path->str, NULL)))
1113 goto next;
1115 args.flags = FT_OPEN_STREAM;
1116 args.stream = stream;
1118 obfuscated = false;
1120 if (FT_Open_Face (libft2, &args, 0, &face) != 0) {
1121 // not a valid font file... is it maybe an obfuscated font?
1122 if (!is_odttf (dirname) || !font_stream_set_guid (stream, dirname)) {
1123 font_stream_destroy (stream);
1124 goto next;
1127 font_stream_reset (stream);
1129 args.flags = FT_OPEN_STREAM;
1130 args.stream = stream;
1132 if (FT_Open_Face (libft2, &args, 0, &face) != 0) {
1133 font_stream_destroy (stream);
1134 goto next;
1137 obfuscated = true;
1140 if (fontdir == NULL)
1141 fontdir = new FontIndex (name);
1143 // cache font info
1144 fontdir->CacheFontInfo (libft2, path->str, stream, face, obfuscated ? dirname : NULL);
1146 font_stream_destroy (stream);
1148 next:
1149 g_string_truncate (path, len);
1152 g_dir_close (dir);
1154 *out = fontdir;
1156 return fontdir != NULL;
1159 static FontIndex *
1160 IndexFontDirectory (FT_Library libft2, const char *name, const char *dirname)
1162 FontIndex *fontdir = NULL;
1163 GString *path;
1164 size_t len;
1166 path = g_string_new (dirname);
1167 len = path->len;
1169 if (!IndexFontSubdirectory (libft2, name, path, &fontdir)) {
1170 g_string_free (path, true);
1171 return NULL;
1174 g_string_truncate (path, len);
1175 fontdir->path = path->str;
1177 g_string_free (path, false);
1179 return fontdir;
1182 static FontIndex *
1183 IndexFontFile (FT_Library libft2, const char *name, const char *path)
1185 const char *filename = path_get_basename (name);
1186 FontIndex *index = NULL;
1187 FT_Open_Args args;
1188 FT_Stream stream;
1189 bool obfuscated;
1190 FT_Face face;
1192 LOG_FONT (stderr, " * indexing font file `%s'...\n", path);
1194 if (!(stream = font_stream_new (path, NULL)))
1195 return NULL;
1197 args.flags = FT_OPEN_STREAM;
1198 args.stream = stream;
1200 obfuscated = false;
1202 if (FT_Open_Face (libft2, &args, 0, &face) != 0) {
1203 // not a valid font file... is it maybe an obfuscated font?
1204 if (!is_odttf (filename) || !font_stream_set_guid (stream, filename)) {
1205 font_stream_destroy (stream);
1206 return NULL;
1209 font_stream_reset (stream);
1211 args.flags = FT_OPEN_STREAM;
1212 args.stream = stream;
1214 if (FT_Open_Face (libft2, &args, 0, &face) != 0) {
1215 font_stream_destroy (stream);
1216 return NULL;
1219 obfuscated = true;
1222 index = new FontIndex (name);
1223 index->path = g_strdup (path);
1225 // cache font info
1226 index->CacheFontInfo (libft2, path, stream, face, obfuscated ? filename : NULL);
1228 font_stream_destroy (stream);
1230 return index;
1233 void
1234 FontManager::AddResource (const char *resource, const char *path)
1236 FontIndex *index;
1237 struct stat st;
1239 LOG_FONT (stderr, "Adding font resource '%s' at %s\n", resource, path);
1241 if ((index = (FontIndex *) g_hash_table_lookup (resources, resource)))
1242 return;
1244 if (stat (path, &st) == -1)
1245 return;
1247 if (S_ISDIR (st.st_mode))
1248 index = IndexFontDirectory (libft2, resource, path);
1249 else if (S_ISREG (st.st_mode))
1250 index = IndexFontFile (libft2, resource, path);
1251 else
1252 return;
1254 if (index)
1255 g_hash_table_insert (resources, index->name, index);
1258 char *
1259 FontManager::AddResource (ManagedStreamCallbacks *stream)
1261 char buf[4096], *resource, *dirname, *path;
1262 unzFile zipfile;
1263 int nread, fd;
1264 gint64 pos;
1266 if (!stream->CanRead (stream->handle))
1267 return NULL;
1269 if (!root && !(root = CreateTempDir ("moonlight-fonts")))
1270 return NULL;
1272 // check if we've already added this resource
1273 resource = g_strdup_printf ("font-source://%p", stream->handle);
1274 if (g_hash_table_lookup (resources, resource) != NULL)
1275 return resource;
1277 snprintf (buf, sizeof (buf), "%p", stream->handle);
1278 path = g_build_filename (root, buf, NULL);
1280 if ((fd = g_open (path, O_CREAT | O_EXCL | O_WRONLY, 0600)) == -1) {
1281 g_free (resource);
1282 g_free (path);
1283 return NULL;
1286 // write the managed stream to disk
1287 pos = stream->Position (stream->handle);
1289 if (stream->CanSeek (stream->handle))
1290 stream->Seek (stream->handle, 0, SEEK_SET);
1292 while ((nread = stream->Read (stream->handle, buf, 0, sizeof (buf))) > 0) {
1293 if (write_all (fd, buf, (size_t) nread) == -1) {
1294 g_free (resource);
1295 close (fd);
1296 g_unlink (path);
1297 g_free (path);
1298 return NULL;
1302 // reset the stream to the original state
1303 if (stream->CanSeek (stream->handle) && pos != -1)
1304 stream->Seek (stream->handle, pos, SEEK_SET);
1306 close (fd);
1308 // check to see if the resource is zipped
1309 if ((zipfile = unzOpen (path))) {
1310 snprintf (buf, sizeof (buf), "%p.zip", stream->handle);
1311 dirname = g_build_filename (root, buf, NULL);
1313 // create a directory to contain our unzipped content
1314 if (g_mkdir (dirname, 0700) == -1) {
1315 unzClose (zipfile);
1316 g_free (resource);
1317 g_free (dirname);
1318 g_unlink (path);
1319 g_free (path);
1320 return NULL;
1323 // unzip the contents
1324 if (!ExtractAll (zipfile, dirname, CanonModeNone)) {
1325 RemoveDir (dirname);
1326 unzClose (zipfile);
1327 g_free (resource);
1328 g_free (dirname);
1329 g_unlink (path);
1330 g_free (path);
1331 return NULL;
1334 unzClose (zipfile);
1335 g_unlink (path);
1336 g_free (path);
1338 path = dirname;
1341 AddResource (resource, path);
1343 g_free (path);
1345 return resource;
1348 static int
1349 style_diff (FontStyleInfo *actual, FontStyleInfo *desired)
1351 #if 0
1352 // we convert to FontConfig for 2 reasons:
1353 // 1. negative values and values > 1023
1354 // 2. smaller ranges
1355 int weight = abs (fc_weight (actual->weight) - fc_weight (desired->weight));
1357 if (actual->slant == desired->slant)
1358 return weight;
1360 if (actual->slant == FontStylesNormal) {
1361 // we can emulate italic/oblique, but we would still prefer the real
1362 // italic font if we can find it so apply a slight penalty
1363 return 1000 + weight;
1366 // ouch, apply a huge penalty
1367 return 1000000 + weight;
1368 #else
1369 // convert to FontConfig values so that each style property fits within 8 bits
1370 int weight = abs (fc_weight (actual->weight) - fc_weight (desired->weight));
1371 int width = abs (fc_width (actual->width) - fc_width (desired->width));
1372 int slant = abs (fc_slant (actual->slant) - fc_slant (desired->slant));
1374 // weight has the highest priority, followed by weight and then slant
1375 return ((width & 0xff) << 16) | ((weight & 0xff) << 8) | (slant & 0xff);
1376 #endif
1379 static void
1380 canon_font_family_and_style (FontStyleInfo *desired, const char *family, FontStretches stretch, FontWeights weight, FontStyles style)
1382 desired->width = FontStretchesNormal;
1383 desired->weight = FontWeightsNormal;
1384 desired->slant = FontStylesNormal;
1385 desired->family_name = NULL;
1386 desired->set = 0;
1388 // extract whatever little style info we can from the family name
1389 style_info_parse (family, desired, true);
1391 // override style with user-specified attributes
1392 if (!(desired->set & Width))
1393 desired->width = stretch;
1394 if (!(desired->set & Weight))
1395 desired->weight = weight;
1396 if (!(desired->set & Slant))
1397 desired->slant = style;
1400 static FaceInfo *
1401 IndexMatchFace (FontIndex *index, const char *family, FontStretches stretch, FontWeights weight, FontStyles style)
1403 FontFile *file = (FontFile *) index->fonts->First ();
1404 FaceInfo *face, *best = NULL;
1405 FontStyleInfo desired;
1406 int closest = G_MAXINT;
1407 int diff;
1408 guint i;
1410 LOG_FONT (stderr, " * searching index for %s; %s\n", family, style_info_to_string (stretch, weight, style));
1412 canon_font_family_and_style (&desired, family, stretch, weight, style);
1414 LOG_FONT (stderr, " * canonicalized family/style: %s; %s\n", desired.family_name,
1415 style_info_to_string (desired.width, desired.weight, desired.slant));
1417 while (file != NULL) {
1418 for (i = 0; i < file->faces->len; i++) {
1419 face = (FaceInfo *) file->faces->pdata[i];
1421 if (!g_ascii_strcasecmp (face->family_name, desired.family_name)) {
1422 diff = style_diff (&face->style, &desired);
1423 if (diff < closest) {
1424 closest = diff;
1425 best = face;
1430 file = (FontFile *) file->next;
1433 g_free (desired.family_name);
1435 return best;
1438 FontFace *
1439 FontManager::OpenFontFace (const char *filename, const char *guid, int index)
1441 FT_Open_Args args;
1442 FT_Stream stream;
1443 FontFace *ff;
1444 FT_Face face;
1445 char *key;
1447 key = g_strdup_printf ("%s#%d", filename, index);
1448 if ((ff = (FontFace *) g_hash_table_lookup (faces, key))) {
1449 g_free (key);
1450 ff->ref ();
1451 return ff;
1454 if (!(stream = font_stream_new (filename, guid))) {
1455 g_free (key);
1456 return NULL;
1459 args.flags = FT_OPEN_STREAM;
1460 args.stream = stream;
1462 if (FT_Open_Face (libft2, &args, index, &face) != 0) {
1463 font_stream_destroy (stream);
1464 g_free (key);
1465 return NULL;
1468 if (!FT_IS_SCALABLE (face)) {
1469 FT_Done_Face (face);
1470 font_stream_destroy (stream);
1471 g_free (key);
1472 return NULL;
1475 return new FontFace (this, face, key);
1478 FontFace *
1479 FontManager::OpenFontResource (const char *resource, const char *family, int idx, FontStretches stretch, FontWeights weight, FontStyles style)
1481 FontIndex *index;
1482 FontFile *file;
1483 FontFace *face;
1484 FaceInfo *fi;
1486 LOG_FONT (stderr, "OpenFontResource (\"%s\", \"%s\", %d, %s)\n", resource ? resource : "(null)",
1487 family ? family : "(null)", idx, style_info_to_string (stretch, weight, style));
1489 if (!(index = (FontIndex *) g_hash_table_lookup (resources, resource))) {
1490 LOG_FONT (stderr, " * error: no such resource\n");
1491 return NULL;
1494 if (family != NULL) {
1495 // open by family
1496 if (!(fi = IndexMatchFace (index, family, stretch, weight, style))) {
1497 LOG_FONT (stderr, " * error: resource does not contain requested font\n");
1498 return NULL;
1500 } else if (idx >= 0) {
1501 // open by index
1502 if (!(file = (FontFile *) index->fonts->First ()) || file->next != NULL)
1503 return NULL;
1505 if ((int) file->faces->len <= idx)
1506 return NULL;
1508 fi = (FaceInfo *) file->faces->pdata[idx];
1509 } else {
1510 // no family or index specified... error?
1511 return NULL;
1514 if (!(face = OpenFontFace (fi->file->path, fi->file->guid, fi->index)))
1515 return NULL;
1517 LOG_FONT (stderr, " * opened %s; %s\n", face->GetFamilyName (), face->GetStyleName ());
1519 return face;
1522 FontFace *
1523 FontManager::OpenSystemFont (const char *family, FontStretches stretch, FontWeights weight, FontStyles style)
1525 FcPattern *pattern, *matched;
1526 FontStyleInfo desired;
1527 FcChar8 *filename;
1528 FcResult result;
1529 FontFace *face;
1530 int index;
1531 char *key;
1533 key = g_strdup_printf ("%s:%d:%d:%d", family, stretch, weight, style);
1534 LOG_FONT (stderr, "Attempting to open system font: %s %s ... ", family, style_info_to_string (stretch, weight, style));
1535 if (g_hash_table_lookup_extended (system_faces, key, NULL, (gpointer *) &face)) {
1536 LOG_FONT (stderr, "found!\n");
1537 g_free (key);
1538 if (face)
1539 face->ref ();
1540 return face;
1542 LOG_FONT (stderr, "not found in cache.\n");
1544 for (int attempt = 0; attempt < 2; attempt++) {
1545 if (attempt == 0) {
1546 desired.family_name = g_strdup (family);
1547 desired.width = stretch;
1548 desired.weight = weight;
1549 desired.slant = style;
1550 } else {
1551 g_free (desired.family_name);
1552 canon_font_family_and_style (&desired, family, stretch, weight, style);
1555 LOG_FONT (stderr, "Attempting to load installed font: %s %s... ", desired.family_name,
1556 style_info_to_string (desired.width, desired.weight, desired.slant));
1558 pattern = FcPatternCreate ();
1559 FcPatternAddDouble (pattern, FC_DPI, dpi);
1560 FcPatternAddString (pattern, FC_FAMILY, (const FcChar8 *) desired.family_name);
1561 FcPatternAddInteger (pattern, FC_WIDTH, fc_width (desired.width));
1562 FcPatternAddInteger (pattern, FC_WEIGHT, fc_weight (desired.weight));
1563 FcPatternAddInteger (pattern, FC_SLANT, fc_slant (desired.slant));
1564 FcDefaultSubstitute (pattern);
1566 if (!(matched = FcFontMatch (NULL, pattern, &result))) {
1567 LOG_FONT (stderr, "no matches\n");
1568 FcPatternDestroy (pattern);
1569 continue;
1572 FcPatternDestroy (pattern);
1574 if (FcPatternGetString (matched, FC_FILE, 0, &filename) != FcResultMatch) {
1575 LOG_FONT (stderr, "no filename\n");
1576 FcPatternDestroy (matched);
1577 continue;
1580 if (FcPatternGetInteger (matched, FC_INDEX, 0, &index) != FcResultMatch) {
1581 LOG_FONT (stderr, "no index\n");
1582 FcPatternDestroy (matched);
1583 continue;
1586 if ((face = OpenFontFace ((const char *) filename, NULL, index))) {
1587 if (!g_ascii_strcasecmp (face->GetFamilyName (), desired.family_name)) {
1588 LOG_FONT (stderr, "got %s %s\n", face->GetFamilyName (), face->GetStyleName ());
1589 face->ref ();
1590 g_hash_table_insert (system_faces, key, face); // the key is freed when the hash table is destroyed
1591 g_free (desired.family_name);
1592 FcPatternDestroy (matched);
1593 return face;
1596 LOG_FONT (stderr, "family mismatch\n");
1597 face->unref ();
1598 } else {
1599 LOG_FONT (stderr, "family not found\n");
1602 FcPatternDestroy (matched);
1605 g_hash_table_insert (system_faces, key, NULL); // the key is freed when the hash table is destroyed
1606 g_free (desired.family_name);
1608 return NULL;
1611 FontFace *
1612 FontManager::OpenFont (const char *name, FontStretches stretch, FontWeights weight, FontStyles style)
1614 const char *family;
1615 FontFace *face;
1616 //char *end;
1617 //int index;
1619 if ((family = strchr (name, '#'))) {
1620 char *resource = g_strndup (name, family - name);
1622 family++;
1624 //if ((index = strtol (family, &end, 10)) >= 0 && index < G_MAXINT && *end == '\0')
1625 // face = OpenFontResource (resource, NULL, index, stretch, weight, style);
1626 //else
1627 face = OpenFontResource (resource, family, -1, stretch, weight, style);
1629 g_free (resource);
1630 } else {
1631 face = OpenSystemFont (name, stretch, weight, style);
1634 return face;
1637 FontFace *
1638 FontManager::OpenFont (const char *name, int index)
1640 return OpenFontResource (name, NULL, index, FontStretchesNormal, FontWeightsNormal, FontStylesNormal);