2009-09-12 Chris Toshok <toshok@ximian.com>
[moon.git] / src / fontmanager.cpp
blobd91fe90df313ea27209012b138cfea774c07b707
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.h>
16 #include <glib/gstdio.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <unistd.h>
24 #include <dirent.h>
25 #include <fcntl.h>
26 #include <errno.h>
27 #include <ctype.h>
29 #include "fontmanager.h"
30 #include "zip/unzip.h"
31 #include "debug.h"
32 #include "utils.h"
33 #include "list.h"
35 #include <fontconfig/fontconfig.h>
36 #include <fontconfig/fcfreetype.h>
37 #include FT_TRUETYPE_TABLES_H
38 #include FT_OUTLINE_H
39 #include FT_SYSTEM_H
40 #include FT_GLYPH_H
44 // OpenType's OS/2 fsSelection Table:
46 // http://www.microsoft.com/typography/otspec/os2.htm#fss
48 enum fsSelection {
49 fsSelectionItalic = (1 << 0),
50 fsSelectionUnderscore = (1 << 1),
51 fsSelectionNegative = (1 << 2),
52 fsSelectionOutlined = (1 << 3),
53 fsSelectionStrikeout = (1 << 4),
54 fsSelectionBold = (1 << 5),
55 fsSelectionRegular = (1 << 6),
56 fsSelectionUseTypoMetrics = (1 << 7),
57 fsSelectionWWS = (1 << 8),
58 fsSelectionOblique = (1 << 9),
61 #define FONT_FACE_SIZE 41.0
63 #define DOUBLE_TO_26_6(d) ((FT_F26Dot6)((d) * 64.0))
64 #define DOUBLE_FROM_26_6(t) ((double)(t) / 64.0)
65 #define DOUBLE_TO_16_16(d) ((FT_Fixed)((d) * 65536.0))
66 #define DOUBLE_FROM_16_16(t) ((double)(t) / 65536.0)
68 #define EMBOLDEN_STRENGTH 0.75
69 #define EMBOLDEN_STRENGTH_26_6 DOUBLE_TO_26_6 (EMBOLDEN_STRENGTH)
70 #define EMBOLDEN_STRENGTH_16_16 DOUBLE_TO_16_16 (EMBOLDEN_STRENGTH)
72 #define ITALIC_SLANT -17.5
73 #define ITALIC_SLANT_RADIANS (ITALIC_SLANT * M_PI / 180.0)
75 static const FT_Matrix italicize = {
76 DOUBLE_TO_16_16 (1.0), DOUBLE_TO_16_16 (tan (ITALIC_SLANT_RADIANS)),
77 DOUBLE_TO_16_16 (0.0), DOUBLE_TO_16_16 (1.0)
80 #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)
85 // Silverlight -> FontConfig enumeration conversion utilities
88 #ifndef FC_WEIGHT_EXTRABLACK
89 #define FC_WEIGHT_EXTRABLACK 215
90 #endif
91 #ifndef FC_WEIGHT_ULTRABLACK
92 #define FC_WEIGHT_ULTRABLACK FC_WEIGHT_EXTRABLACK
93 #endif
95 // Silverlight accepts negative values ]0,-475[ as bold and everything over 1023 as normal
96 #define FONT_LOWER_BOLD_LIMIT -475
97 #define FONT_UPPER_BOLD_LIMIT 1024
99 bool
100 FontWeightIsBold (FontWeights weight)
102 if (weight > FONT_LOWER_BOLD_LIMIT)
103 return weight < 0 || (weight >= FontWeightsSemiBold && weight < FONT_UPPER_BOLD_LIMIT);
105 return false;
108 static int
109 fc_weight (FontWeights weight)
111 if ((weight < 0) && (weight > FONT_LOWER_BOLD_LIMIT))
112 return FC_WEIGHT_BLACK;
113 else if (weight < (FontWeightsThin + FontWeightsLight) / 2)
114 return FC_WEIGHT_ULTRALIGHT;
115 else if (weight < (FontWeightsLight + FontWeightsNormal) / 2)
116 return FC_WEIGHT_LIGHT;
117 else if (weight < (FontWeightsNormal + FontWeightsMedium) / 2)
118 return FC_WEIGHT_NORMAL;
119 else if (weight < (FontWeightsMedium + FontWeightsSemiBold) / 2)
120 return FC_WEIGHT_MEDIUM;
121 else if (weight < (FontWeightsSemiBold + FontWeightsBold) / 2)
122 return FC_WEIGHT_SEMIBOLD;
123 else if (weight < (FontWeightsBold + FontWeightsExtraBold) / 2)
124 return FC_WEIGHT_BOLD;
125 else if (weight < (FontWeightsExtraBold + FontWeightsBlack) / 2)
126 return FC_WEIGHT_ULTRABOLD;
127 else if (weight < FONT_UPPER_BOLD_LIMIT)
128 return FC_WEIGHT_BLACK;
129 else
130 return FC_WEIGHT_NORMAL;
133 static int
134 fc_width (FontStretches stretch)
136 switch (stretch) {
137 case FontStretchesUltraCondensed:
138 return FC_WIDTH_ULTRACONDENSED;
139 case FontStretchesExtraCondensed:
140 return FC_WIDTH_EXTRACONDENSED;
141 case FontStretchesCondensed:
142 return FC_WIDTH_CONDENSED;
143 case FontStretchesSemiCondensed:
144 return FC_WIDTH_SEMICONDENSED;
145 case FontStretchesNormal:
146 return FC_WIDTH_NORMAL;
147 #if 0
148 case FontStretchesMedium:
149 return FC_WIDTH_NORMAL;
150 #endif
151 case FontStretchesSemiExpanded:
152 return FC_WIDTH_SEMIEXPANDED;
153 case FontStretchesExpanded:
154 return FC_WIDTH_EXPANDED;
155 case FontStretchesExtraExpanded:
156 return FC_WIDTH_EXTRAEXPANDED;
157 case FontStretchesUltraExpanded:
158 return FC_WIDTH_ULTRAEXPANDED;
159 default:
160 return FC_WIDTH_NORMAL;
164 static int
165 fc_slant (FontStyles style)
167 switch (style) {
168 case FontStylesNormal:
169 return FC_SLANT_ROMAN;
170 // technically Olbique does not exists in SL 1.0 or 2.0 (it's in WPF) but the parser allows it
171 case FontStylesOblique:
172 return FC_SLANT_OBLIQUE;
173 case FontStylesItalic:
174 // Silverlight defaults bad values to Italic
175 default:
176 return FC_SLANT_ITALIC;
182 // Font-style parser utils
185 #define Width (1 << 0)
186 #define Weight (1 << 1)
187 #define Slant (1 << 2)
189 struct FontStyleInfo {
190 char *family_name;
191 FontStretches width;
192 FontWeights weight;
193 FontStyles slant;
194 int set;
197 static struct {
198 const char *name;
199 size_t len;
200 int type;
201 int value;
202 } style_hints[] = {
203 // widths
204 { "Ultra-Condensed", 15, Width, FontStretchesUltraCondensed },
205 { "Extra-Condensed", 15, Width, FontStretchesExtraCondensed },
206 { "Semi-Condensed", 14, Width, FontStretchesSemiCondensed },
207 { "UltraCondensed", 14, Width, FontStretchesUltraCondensed },
208 { "ExtraCondensed", 14, Width, FontStretchesExtraCondensed },
209 { "SemiCondensed", 13, Width, FontStretchesSemiCondensed },
210 { "Condensed", 9, Width, FontStretchesCondensed },
211 { "Cond", 4, Width, FontStretchesCondensed },
212 { "Ultra-Expanded", 14, Width, FontStretchesUltraExpanded },
213 { "Extra-Expanded", 14, Width, FontStretchesExtraExpanded },
214 { "Semi-Expanded", 13, Width, FontStretchesSemiExpanded },
215 { "UltraExpanded", 13, Width, FontStretchesUltraExpanded },
216 { "ExtraExpanded", 13, Width, FontStretchesExtraExpanded },
217 { "SemiExpanded", 12, Width, FontStretchesSemiExpanded },
218 { "Expanded", 8, Width, FontStretchesExpanded },
220 // weights
221 { "Thin", 4, Weight, FontWeightsThin },
222 { "Ultra-Light", 11, Weight, FontWeightsExtraLight },
223 { "Extra-Light", 11, Weight, FontWeightsExtraLight },
224 { "UltraLight", 10, Weight, FontWeightsExtraLight },
225 { "ExtraLight", 10, Weight, FontWeightsExtraLight },
226 { "Light", 5, Weight, FontWeightsLight },
227 { "Book", 4, Weight, FontWeightsNormal },
228 { "Medium", 6, Weight, FontWeightsMedium },
229 { "Demi-Bold", 9, Weight, FontWeightsSemiBold },
230 { "Semi-Bold", 9, Weight, FontWeightsSemiBold },
231 { "DemiBold", 8, Weight, FontWeightsSemiBold },
232 { "SemiBold", 8, Weight, FontWeightsSemiBold },
233 { "Bold", 4, Weight, FontWeightsBold },
234 { "Extra-Bold", 10, Weight, FontWeightsExtraBold },
235 { "Ultra-Bold", 10, Weight, FontWeightsExtraBold },
236 { "ExtraBold", 9, Weight, FontWeightsExtraBold },
237 { "UltraBold", 9, Weight, FontWeightsExtraBold },
238 { "Black", 5, Weight, FontWeightsBlack },
239 { "Heavy", 5, Weight, FontWeightsBlack },
240 { "Extra-Black", 11, Weight, FontWeightsExtraBlack },
241 { "Ultra-Black", 11, Weight, FontWeightsExtraBlack },
242 { "ExtraBlack", 10, Weight, FontWeightsExtraBlack },
243 { "UltraBlack", 10, Weight, FontWeightsExtraBlack },
245 // slants
246 { "Oblique", 7, Slant, FontStylesOblique },
247 { "Italic", 6, Slant, FontStylesItalic },
248 { "Kursiv", 6, Slant, FontStylesItalic },
250 // changes nothing
251 { "Regular", 7, 0, 0 },
252 { "W3", 2, 0, 0 }, // as in Hiragino Mincho Pro W3
255 static void
256 style_info_parse (const char *style, FontStyleInfo *info, bool family)
258 register const char *inptr = style;
259 const char *first_hint = NULL;
260 const char *token;
261 guint tokens = 0;
262 size_t len;
263 guint i;
265 if (!style)
266 return;
268 while (*inptr) {
269 while (*inptr && isspace ((int) ((unsigned char) *inptr)))
270 inptr++;
272 if (*inptr == '\0')
273 break;
275 token = inptr;
276 while (*inptr && !isspace ((int) ((unsigned char) *inptr)))
277 inptr++;
279 tokens++;
281 if (family && tokens == 1) {
282 // if parsing the family_name, first token must not be interpreted as a style hint
283 continue;
286 len = (size_t) (inptr - token);
287 for (i = 0; i < G_N_ELEMENTS (style_hints); i++) {
288 if (style_hints[i].len == len && !strncmp (style_hints[i].name, token, len)) {
289 switch (style_hints[i].type) {
290 case Width:
291 info->width = (FontStretches) style_hints[i].value;
292 info->set |= Width;
293 break;
294 case Weight:
295 info->weight = (FontWeights) style_hints[i].value;
296 info->set |= Weight;
297 break;
298 case Slant:
299 info->slant = (FontStyles) style_hints[i].value;
300 info->set |= Slant;
301 break;
304 if (!first_hint)
305 first_hint = token;
306 break;
310 if (family && i == G_N_ELEMENTS (style_hints)) {
311 // if we come across an unknown style hint when
312 // parsing the family_name, assume that any previously
313 // found style hints were not actually style hints,
314 // but instead just part of the family name.
315 info->width = FontStretchesNormal;
316 info->weight = FontWeightsNormal;
317 info->slant = FontStylesNormal;
318 info->set = 0;
320 first_hint = NULL;
324 if (family) {
325 if (first_hint)
326 info->family_name = g_strndup (style, first_hint - style);
327 else
328 info->family_name = g_strdup (style);
330 g_strstrip (info->family_name);
334 #ifdef DEBUG
335 static const char *
336 style_info_to_string (FontStretches stretch, FontWeights weight, FontStyles style)
338 static char namebuf[256];
339 guint i = 0;
340 char *p;
342 namebuf[0] = '\0';
343 p = namebuf;
345 if (stretch != FontStretchesNormal) {
346 while (style_hints[i].type == Width) {
347 if (style_hints[i].value == stretch) {
348 p = g_stpcpy (p, style_hints[i].name);
349 break;
352 i++;
356 if (weight != FontWeightsNormal) {
357 while (style_hints[i].type != Weight)
358 i++;
360 while (style_hints[i].type == Weight) {
361 if (style_hints[i].value == weight) {
362 if (p != namebuf)
363 *p++ = ' ';
365 p = g_stpcpy (p, style_hints[i].name);
366 break;
369 i++;
373 if (style != FontStylesNormal) {
374 while (style_hints[i].type != Slant)
375 i++;
377 while (i < G_N_ELEMENTS (style_hints)) {
378 if (style_hints[i].value == style) {
379 if (p != namebuf)
380 *p++ = ' ';
382 p = g_stpcpy (p, style_hints[i].name);
383 break;
386 i++;
390 return namebuf;
392 #endif
394 static bool
395 is_odttf (const char *name)
397 size_t len = strlen (name);
399 if (len > 6 && !g_ascii_strcasecmp (name + len - 6, ".odttf"))
400 return true;
402 return false;
407 // FontStream
410 struct FontStream {
411 bool obfuscated;
412 char guid[16];
413 FILE *fp;
416 static const char *
417 path_get_basename (const char *path)
419 const char *name;
421 if (!(name = strrchr (path, '/')))
422 return path;
424 return name + 1;
427 static bool
428 decode_guid (const char *in, char *guid)
430 const char *inptr = in;
431 int i = 16;
433 while (i > 0 && *inptr && *inptr != '.') {
434 if (*inptr == '-')
435 inptr++;
437 i--;
439 if (*inptr >= '0' && *inptr <= '9')
440 guid[i] = (*inptr - '0') * 16;
441 else if (*inptr >= 'a' && *inptr <= 'f')
442 guid[i] = ((*inptr - 'a') + 10) * 16;
443 else if (*inptr >= 'A' && *inptr <= 'F')
444 guid[i] = ((*inptr - 'A') + 10) * 16;
445 else
446 return false;
448 inptr++;
450 if (*inptr >= '0' && *inptr <= '9')
451 guid[i] += (*inptr - '0');
452 else if (*inptr >= 'a' && *inptr <= 'f')
453 guid[i] += ((*inptr - 'a') + 10);
454 else if (*inptr >= 'A' && *inptr <= 'F')
455 guid[i] += ((*inptr - 'A') + 10);
456 else
457 return false;
459 inptr++;
462 if (i > 0)
463 return false;
465 return true;
468 static bool
469 font_stream_set_guid (FT_Stream stream, const char *guid)
471 FontStream *fs = (FontStream *) stream->descriptor.pointer;
473 if (guid) {
474 fs->obfuscated = decode_guid (guid, fs->guid);
475 } else {
476 fs->obfuscated = false;
479 return fs->obfuscated;
482 static unsigned long
483 font_stream_read (FT_Stream stream, unsigned long offset, unsigned char *buffer, unsigned long count)
485 FontStream *fs = (FontStream *) stream->descriptor.pointer;
486 size_t nread;
488 if (fseek (fs->fp, (long) offset, SEEK_SET) == -1)
489 return 0;
491 if (count == 0 || buffer == NULL)
492 return 0;
494 nread = fread (buffer, 1, count, fs->fp);
496 if (fs->obfuscated && offset < 32 && nread > 0) {
497 /* obfuscated font... need to deobfuscate */
498 unsigned long i = offset;
499 unsigned long j = 0;
501 for ( ; i < 32 && j < nread; i++, j++)
502 buffer[j] = buffer[j] ^ fs->guid[i % 16];
505 return nread;
508 static void
509 font_stream_reset (FT_Stream stream)
511 FontStream *fs = (FontStream *) stream->descriptor.pointer;
513 rewind (fs->fp);
514 stream->pos = 0;
517 static void
518 font_stream_close (FT_Stream stream)
520 // no-op
523 static FT_Stream
524 font_stream_new (const char *filename, const char *guid)
526 FT_Stream stream;
527 FontStream *fs;
528 FILE *fp;
530 if (!(fp = fopen (filename, "r")))
531 return NULL;
533 fs = (FontStream *) g_malloc (sizeof (FontStream));
534 fs->obfuscated = false;
535 fs->fp = fp;
537 stream = (FT_Stream) g_malloc0 (sizeof (FT_StreamRec));
538 stream->close = font_stream_close;
539 stream->read = font_stream_read;
540 stream->descriptor.pointer = fs;
542 fseek (fp, 0, SEEK_END);
543 stream->size = ftell (fp);
544 fseek (fp, 0, SEEK_SET);
546 font_stream_set_guid (stream, guid);
548 return stream;
551 static void
552 font_stream_destroy (FT_Stream stream)
554 FontStream *fs = (FontStream *) stream->descriptor.pointer;
556 g_free (stream);
557 fclose (fs->fp);
558 g_free (fs);
563 // FaceInfo
566 struct FontFile : public List::Node {
567 GPtrArray *faces;
568 char *path, *guid;
570 FontFile (const char *path, const char *guid);
571 ~FontFile ();
574 struct FaceInfo {
575 FontStyleInfo style;
576 char *family_name;
577 FontFile *file;
578 int index;
580 FaceInfo (FontFile *file, FT_Face face, int index);
581 ~FaceInfo ();
584 FaceInfo::FaceInfo (FontFile *file, FT_Face face, int index)
586 LOG_FONT (stderr, " * indexing %s[%d]: family=\"%s\"; style=\"%s\"\n",
587 path_get_basename (file->path), index, face->family_name, face->style_name);
589 style.width = FontStretchesNormal;
590 style.weight = FontWeightsNormal;
591 style.slant = FontStylesNormal;
592 style.family_name = NULL;
593 style.set = 0;
595 // extract whatever little style info we can from the family name
596 style_info_parse (face->family_name, &style, true);
598 // style info parsed from style_name overrides anything we got from family_name
599 style_info_parse (face->style_name, &style, false);
601 family_name = style.family_name;
603 LOG_FONT (stderr, " * indexed as %s; %s\n", family_name,
604 style_info_to_string (style.width, style.weight, style.slant));
606 this->index = index;
607 this->file = file;
610 FaceInfo::~FaceInfo ()
612 g_free (family_name);
617 // FontFile
620 FontFile::FontFile (const char *path, const char *guid)
622 this->path = g_strdup (path);
623 this->guid = g_strdup (guid);
624 faces = NULL;
627 FontFile::~FontFile ()
629 if (faces != NULL) {
630 FaceInfo *face;
632 for (guint i = 0; i < faces->len; i++) {
633 face = (FaceInfo *) faces->pdata[i];
634 delete face;
637 g_ptr_array_free (faces, true);
640 g_free (path);
641 g_free (guid);
646 // FontIndex
649 struct FontIndex {
650 List *fonts;
651 char *name;
652 char *path;
654 FontIndex (const char *name);
655 ~FontIndex ();
657 void CacheFontInfo (FT_Library libft2, const char *filename, FT_Stream stream, FT_Face face, const char *guid);
660 FontIndex::FontIndex (const char *name)
662 this->name = g_strdup (name);
663 fonts = new List ();
664 path = NULL;
667 FontIndex::~FontIndex ()
669 g_free (name);
670 g_free (path);
671 delete fonts;
674 void
675 FontIndex::CacheFontInfo (FT_Library libft2, const char *filename, FT_Stream stream, FT_Face face, const char *guid)
677 int i = 0, nfaces = face->num_faces;
678 FT_Open_Args args;
679 FontFile *file;
680 FaceInfo *fi;
682 LOG_FONT (stderr, " * caching font info for `%s'...\n", filename);
684 file = new FontFile (filename, guid);
685 file->faces = g_ptr_array_new ();
687 do {
688 args.flags = FT_OPEN_STREAM;
689 args.stream = stream;
691 if (i > 0 && FT_Open_Face (libft2, &args, i, &face) != 0)
692 break;
694 fi = new FaceInfo (file, face, i);
695 g_ptr_array_add (file->faces, fi);
697 FT_Done_Face (face);
699 font_stream_reset (stream);
701 i++;
702 } while (i < nfaces);
704 fonts->Append (file);
707 static void
708 font_index_destroy (gpointer user_data)
710 delete ((FontIndex *) user_data);
715 // FontFace
718 FontFace::FontFace (FontManager *manager, FT_Face face, char *key)
720 FT_Set_Pixel_Sizes (face, 0, (int) FONT_FACE_SIZE);
721 this->cur_size = FONT_FACE_SIZE;
722 this->manager = manager;
723 this->ref_count = 1;
724 this->face = face;
725 this->key = key;
727 g_hash_table_insert (manager->faces, key, this);
730 FontFace::~FontFace ()
732 FT_Stream stream;
734 g_hash_table_steal (manager->faces, key);
736 stream = face->stream;
737 FT_Done_Face (face);
738 font_stream_destroy (stream);
739 g_free (key);
742 void
743 FontFace::ref ()
745 ref_count++;
748 void
749 FontFace::unref ()
751 ref_count--;
753 if (ref_count == 0)
754 delete this;
757 const char *
758 FontFace::GetFamilyName ()
760 return face->family_name;
763 const char *
764 FontFace::GetStyleName ()
766 return face->style_name;
769 bool
770 FontFace::IsScalable ()
772 return FT_IS_SCALABLE (face);
775 bool
776 FontFace::IsItalic ()
778 return (face->style_flags & FT_STYLE_FLAG_ITALIC);
781 bool
782 FontFace::IsBold ()
784 return (face->style_flags & FT_STYLE_FLAG_BOLD);
787 gunichar
788 FontFace::GetCharFromIndex (guint32 index)
790 gunichar unichar;
791 guint32 idx;
793 if (index == 0)
794 return 0;
796 unichar = FT_Get_First_Char (face, &idx);
797 while (idx != index && idx != 0)
798 unichar = FT_Get_Next_Char (face, unichar, &idx);
800 if (idx == 0)
801 unichar = 0;
803 return unichar;
806 guint32
807 FontFace::GetCharIndex (gunichar unichar)
809 return FcFreeTypeCharIndex (face, unichar);
812 bool
813 FontFace::HasChar (gunichar unichar)
815 return FcFreeTypeCharIndex (face, unichar) != 0;
818 void
819 FontFace::GetExtents (double size, FontFaceExtents *extents)
821 double scale = size / face->units_per_EM;
823 if (FT_IS_SFNT (face)) {
824 TT_HoriHeader *hhea = (TT_HoriHeader *) FT_Get_Sfnt_Table (face, ft_sfnt_hhea);
825 TT_OS2 *os2 = (TT_OS2 *) FT_Get_Sfnt_Table (face, ft_sfnt_os2);
826 int height, ascender, descender;
828 if (os2 && (os2->fsSelection & fsSelectionUseTypoMetrics)) {
829 // Use the typographic Ascender, Descender, and LineGap values for everything.
830 height = os2->sTypoAscender - os2->sTypoDescender + os2->sTypoLineGap;
831 descender = -os2->sTypoDescender;
832 ascender = os2->sTypoAscender;
833 } else {
834 // Calculate the LineSpacing for both the hhea table and the OS/2 table.
835 int hhea_height = hhea->Ascender + abs (hhea->Descender) + hhea->Line_Gap;
836 int os2_height = os2 ? (os2->usWinAscent + os2->usWinDescent) : 0;
838 // The LineSpacing is the maximum of the two sumations.
839 height = MAX (hhea_height, os2_height);
841 // If the OS/2 table exists, use usWinAscent as the
842 // ascender. Otherwise use hhea's Ascender value.
843 ascender = os2 ? os2->usWinAscent : hhea->Ascender;
845 // The Descender becomes the difference between the
846 // LineSpacing and the Ascender.
847 descender = height - ascender;
850 extents->descent = -descender * scale;
851 extents->ascent = ascender * scale;
852 extents->height = height * scale;
853 } else {
854 // Fall back to the default FreeType2 values.
855 extents->descent = face->descender * scale;
856 extents->ascent = face->ascender * scale;
857 extents->height = face->height * scale;
860 extents->underline_thickness = face->underline_thickness * scale;
861 extents->underline_position = -face->underline_position * scale;
862 extents->underline_position += ((extents->underline_thickness + 1) / 2.0);
864 if (extents->underline_thickness < 1.0)
865 extents->underline_thickness = 1.0;
868 double
869 FontFace::Kerning (double size, guint32 left, guint32 right)
871 FT_Vector kerning;
873 if (!FT_HAS_KERNING (face) || left == 0 || right == 0)
874 return 0.0;
876 if (size <= FONT_FACE_SIZE) {
877 if (cur_size != FONT_FACE_SIZE) {
878 FT_Set_Pixel_Sizes (face, 0, (int) FONT_FACE_SIZE);
879 cur_size = FONT_FACE_SIZE;
882 FT_Get_Kerning (face, left, right, FT_KERNING_DEFAULT, &kerning);
884 return (kerning.x * size) / (FONT_FACE_SIZE * 64.0);
885 } else {
886 if (cur_size != size) {
887 FT_Set_Pixel_Sizes (face, 0, (int) size);
888 cur_size = size;
891 FT_Get_Kerning (face, left, right, FT_KERNING_DEFAULT, &kerning);
893 return kerning.x / 64.0;
897 static int
898 font_move_to (FT_Vector *to, void *user_data)
900 moon_path *path = (moon_path *) user_data;
901 double x, y;
903 x = DOUBLE_FROM_26_6 (to->x);
904 y = DOUBLE_FROM_26_6 (to->y);
906 moon_move_to (path, x, y);
908 return 0;
911 static int
912 font_line_to (FT_Vector *to, void *user_data)
914 moon_path *path = (moon_path *) user_data;
915 double x, y;
917 x = DOUBLE_FROM_26_6 (to->x);
918 y = DOUBLE_FROM_26_6 (to->y);
920 moon_line_to (path, x, y);
922 return 0;
925 static int
926 font_conic_to (FT_Vector *control, FT_Vector *to, void *user_data)
928 moon_path *path = (moon_path *) user_data;
929 double x3, y3;
930 double x, y;
932 x = DOUBLE_FROM_26_6 (control->x);
933 y = DOUBLE_FROM_26_6 (control->y);
935 x3 = DOUBLE_FROM_26_6 (to->x);
936 y3 = DOUBLE_FROM_26_6 (to->y);
938 moon_quad_curve_to (path, x, y, x3, y3);
940 return 0;
943 static int
944 font_cubic_to (FT_Vector *control1, FT_Vector *control2, FT_Vector *to, void *user_data)
946 moon_path *path = (moon_path *) user_data;
947 double x0, y0;
948 double x1, y1;
949 double x2, y2;
951 x0 = DOUBLE_FROM_26_6 (control1->x);
952 y0 = DOUBLE_FROM_26_6 (control1->y);
954 x1 = DOUBLE_FROM_26_6 (control2->x);
955 y1 = DOUBLE_FROM_26_6 (control2->y);
957 x2 = DOUBLE_FROM_26_6 (to->x);
958 y2 = DOUBLE_FROM_26_6 (to->y);
960 moon_curve_to (path, x0, y0, x1, y1, x2, y2);
962 return 0;
965 static const FT_Outline_Funcs outline_funcs = {
966 (FT_Outline_MoveToFunc) font_move_to,
967 (FT_Outline_LineToFunc) font_line_to,
968 (FT_Outline_ConicToFunc) font_conic_to,
969 (FT_Outline_CubicToFunc) font_cubic_to,
970 0, /* shift */
971 0, /* delta */
974 bool
975 FontFace::LoadGlyph (double size, GlyphInfo *glyph, StyleSimulations simulate)
977 FT_Glyph_Metrics *metrics;
978 FT_Fixed hori_adj = 0;
979 FT_Pos bbox_adj = 0;
980 FT_Matrix matrix;
981 double scale;
983 if (!face)
984 return false;
986 if (size <= FONT_FACE_SIZE) {
987 if (cur_size != FONT_FACE_SIZE) {
988 FT_Set_Pixel_Sizes (face, 0, (int) FONT_FACE_SIZE);
989 cur_size = FONT_FACE_SIZE;
992 scale = size / FONT_FACE_SIZE;
993 } else {
994 if (cur_size != size) {
995 FT_Set_Pixel_Sizes (face, 0, (int) size);
996 cur_size = size;
999 scale = 1.0;
1002 if (FT_Load_Glyph (face, glyph->index, LOAD_FLAGS) != 0)
1003 return false;
1005 if (FT_Render_Glyph (face->glyph, FT_RENDER_MODE_NORMAL) != 0)
1006 return false;
1008 // invert the glyph over the y-axis and scale
1009 matrix.xx = DOUBLE_TO_16_16 (scale);
1010 matrix.xy = 0;
1011 matrix.yx = 0;
1012 matrix.yy = -DOUBLE_TO_16_16 (scale);
1014 if ((simulate & StyleSimulationsBold) != 0) {
1015 FT_Outline_Embolden (&face->glyph->outline, EMBOLDEN_STRENGTH_26_6);
1016 hori_adj = EMBOLDEN_STRENGTH_16_16;
1017 bbox_adj = EMBOLDEN_STRENGTH_26_6;
1020 if ((simulate & StyleSimulationsItalic) != 0)
1021 FT_Matrix_Multiply (&italicize, &matrix);
1023 glyph->path = moon_path_new (8);
1024 FT_Outline_Transform (&face->glyph->outline, &matrix);
1025 FT_Outline_Decompose (&face->glyph->outline, &outline_funcs, glyph->path);
1027 metrics = &face->glyph->metrics;
1029 glyph->metrics.horiBearingX = DOUBLE_FROM_26_6 (metrics->horiBearingX) * scale;
1030 //glyph->metrics.horiBearingY = DOUBLE_FROM_26_6 (metrics->horiBearingY) * scale;
1031 // always prefer linearHoriAdvance over horiAdvance since the later is rounded to an integer
1032 glyph->metrics.horiAdvance = DOUBLE_FROM_16_16 (face->glyph->linearHoriAdvance + hori_adj) * scale;
1033 //glyph->metrics.height = DOUBLE_FROM_26_6 (metrics->height + bbox_adj) * scale;
1034 //glyph->metrics.width = DOUBLE_FROM_26_6 (metrics->width + bbox_adj) * scale;
1036 return true;
1039 static void
1040 font_face_destroy (gpointer data)
1042 FontFace *face = (FontFace *) data;
1044 delete face;
1049 // FontManager
1052 FontManager::FontManager ()
1054 FcPattern *pattern;
1056 resources = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, font_index_destroy);
1057 faces = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, font_face_destroy);
1059 FT_Init_FreeType (&libft2);
1061 pattern = FcPatternBuild (NULL, FC_FAMILY, FcTypeString, "Sans",
1062 FC_SIZE, FcTypeDouble, 10.0, NULL);
1064 if (FcPatternGetDouble (pattern, FC_DPI, 0, &dpi) != FcResultMatch)
1065 dpi = 72.0;
1067 FcPatternDestroy (pattern);
1068 root = NULL;
1071 FontManager::~FontManager ()
1073 g_hash_table_destroy (resources);
1074 g_hash_table_destroy (faces);
1075 FT_Done_FreeType (libft2);
1077 if (root) {
1078 RemoveDir (root);
1079 g_free (root);
1083 static bool
1084 IndexFontSubdirectory (FT_Library libft2, const char *name, GString *path, FontIndex **out)
1086 FontIndex *fontdir = *out;
1087 struct dirent *dent;
1088 FT_Open_Args args;
1089 FT_Stream stream;
1090 bool obfuscated;
1091 struct stat st;
1092 FT_Face face;
1093 size_t len;
1094 DIR *dir;
1096 if (!(dir = opendir (path->str)))
1097 return fontdir != NULL;
1099 LOG_FONT (stderr, " * indexing font directory `%s'...\n", path->str);
1101 g_string_append_c (path, G_DIR_SEPARATOR);
1102 len = path->len;
1104 while ((dent = readdir (dir))) {
1105 if (!strcmp (dent->d_name, "..") ||
1106 !strcmp (dent->d_name, "."))
1107 continue;
1109 g_string_append (path, dent->d_name);
1111 if (stat (path->str, &st) == -1)
1112 goto next;
1114 if (S_ISDIR (st.st_mode)) {
1115 IndexFontSubdirectory (libft2, name, path, &fontdir);
1116 goto next;
1119 if (!(stream = font_stream_new (path->str, NULL)))
1120 goto next;
1122 args.flags = FT_OPEN_STREAM;
1123 args.stream = stream;
1125 obfuscated = false;
1127 if (FT_Open_Face (libft2, &args, 0, &face) != 0) {
1128 // not a valid font file... is it maybe an obfuscated font?
1129 if (!is_odttf (dent->d_name) || !font_stream_set_guid (stream, dent->d_name)) {
1130 font_stream_destroy (stream);
1131 goto next;
1134 font_stream_reset (stream);
1136 args.flags = FT_OPEN_STREAM;
1137 args.stream = stream;
1139 if (FT_Open_Face (libft2, &args, 0, &face) != 0) {
1140 font_stream_destroy (stream);
1141 goto next;
1144 obfuscated = true;
1147 if (fontdir == NULL)
1148 fontdir = new FontIndex (name);
1150 // cache font info
1151 fontdir->CacheFontInfo (libft2, path->str, stream, face, obfuscated ? dent->d_name : NULL);
1153 font_stream_destroy (stream);
1155 next:
1156 g_string_truncate (path, len);
1159 closedir (dir);
1161 *out = fontdir;
1163 return fontdir != NULL;
1166 static FontIndex *
1167 IndexFontDirectory (FT_Library libft2, const char *name, const char *dirname)
1169 FontIndex *fontdir = NULL;
1170 GString *path;
1171 size_t len;
1173 path = g_string_new (dirname);
1174 len = path->len;
1176 if (!IndexFontSubdirectory (libft2, name, path, &fontdir)) {
1177 g_string_free (path, true);
1178 return NULL;
1181 g_string_truncate (path, len);
1182 fontdir->path = path->str;
1184 g_string_free (path, false);
1186 return fontdir;
1189 static FontIndex *
1190 IndexFontFile (FT_Library libft2, const char *name, const char *path)
1192 const char *filename = path_get_basename (name);
1193 FontIndex *index = NULL;
1194 FT_Open_Args args;
1195 FT_Stream stream;
1196 bool obfuscated;
1197 FT_Face face;
1199 LOG_FONT (stderr, " * indexing font file `%s'...\n", path);
1201 if (!(stream = font_stream_new (path, NULL)))
1202 return NULL;
1204 args.flags = FT_OPEN_STREAM;
1205 args.stream = stream;
1207 obfuscated = false;
1209 if (FT_Open_Face (libft2, &args, 0, &face) != 0) {
1210 // not a valid font file... is it maybe an obfuscated font?
1211 if (!is_odttf (filename) || !font_stream_set_guid (stream, filename)) {
1212 font_stream_destroy (stream);
1213 return NULL;
1216 font_stream_reset (stream);
1218 args.flags = FT_OPEN_STREAM;
1219 args.stream = stream;
1221 if (FT_Open_Face (libft2, &args, 0, &face) != 0) {
1222 font_stream_destroy (stream);
1223 return NULL;
1226 obfuscated = true;
1229 index = new FontIndex (name);
1230 index->path = g_strdup (path);
1232 // cache font info
1233 index->CacheFontInfo (libft2, path, stream, face, obfuscated ? filename : NULL);
1235 font_stream_destroy (stream);
1237 return index;
1240 void
1241 FontManager::AddResource (const char *resource, const char *path)
1243 FontIndex *index;
1244 struct stat st;
1246 LOG_FONT (stderr, "Adding font resource '%s' at %s\n", resource, path);
1248 if ((index = (FontIndex *) g_hash_table_lookup (resources, resource)))
1249 return;
1251 if (stat (path, &st) == -1)
1252 return;
1254 if (S_ISDIR (st.st_mode))
1255 index = IndexFontDirectory (libft2, resource, path);
1256 else if (S_ISREG (st.st_mode))
1257 index = IndexFontFile (libft2, resource, path);
1258 else
1259 return;
1261 if (index)
1262 g_hash_table_insert (resources, index->name, index);
1265 char *
1266 FontManager::AddResource (ManagedStreamCallbacks *stream)
1268 char buf[4096], *resource, *dirname, *path;
1269 unzFile zipfile;
1270 int nread, fd;
1271 gint64 pos;
1273 if (!stream->CanRead (stream->handle))
1274 return NULL;
1276 if (!root && !(root = CreateTempDir ("moonlight-fonts")))
1277 return NULL;
1279 // check if we've already added this resource
1280 resource = g_strdup_printf ("font-source://%p", stream->handle);
1281 if (g_hash_table_lookup (resources, resource) != NULL)
1282 return resource;
1284 snprintf (buf, sizeof (buf), "%p", stream->handle);
1285 path = g_build_filename (root, buf, NULL);
1287 if ((fd = open (path, O_CREAT | O_EXCL | O_WRONLY, 0600)) == -1) {
1288 g_free (resource);
1289 g_free (path);
1290 return NULL;
1293 // write the managed stream to disk
1294 pos = stream->Position (stream->handle);
1296 if (stream->CanSeek (stream->handle))
1297 stream->Seek (stream->handle, 0, SEEK_SET);
1299 while ((nread = stream->Read (stream->handle, buf, 0, sizeof (buf))) > 0) {
1300 if (write_all (fd, buf, (size_t) nread) == -1) {
1301 g_free (resource);
1302 close (fd);
1303 g_unlink (path);
1304 g_free (path);
1305 return NULL;
1309 // reset the stream to the original state
1310 if (stream->CanSeek (stream->handle) && pos != -1)
1311 stream->Seek (stream->handle, pos, SEEK_SET);
1313 close (fd);
1315 // check to see if the resource is zipped
1316 if ((zipfile = unzOpen (path))) {
1317 snprintf (buf, sizeof (buf), "%p.zip", stream->handle);
1318 dirname = g_build_filename (root, buf, NULL);
1320 // create a directory to contain our unzipped content
1321 if (mkdir (dirname, 0700) == -1) {
1322 unzClose (zipfile);
1323 g_free (resource);
1324 g_free (dirname);
1325 g_unlink (path);
1326 g_free (path);
1327 return NULL;
1330 // unzip the contents
1331 if (!ExtractAll (zipfile, dirname, false)) {
1332 RemoveDir (dirname);
1333 unzClose (zipfile);
1334 g_free (resource);
1335 g_free (dirname);
1336 g_unlink (path);
1337 g_free (path);
1338 return NULL;
1341 unzClose (zipfile);
1342 g_unlink (path);
1343 g_free (path);
1345 path = dirname;
1348 AddResource (resource, path);
1350 g_free (path);
1352 return resource;
1355 static int
1356 style_diff (FontStyleInfo *actual, FontStyleInfo *desired)
1358 #if 0
1359 // we convert to FontConfig for 2 reasons:
1360 // 1. negative values and values > 1023
1361 // 2. smaller ranges
1362 int weight = abs (fc_weight (actual->weight) - fc_weight (desired->weight));
1364 if (actual->slant == desired->slant)
1365 return weight;
1367 if (actual->slant == FontStylesNormal) {
1368 // we can emulate italic/oblique, but we would still prefer the real
1369 // italic font if we can find it so apply a slight penalty
1370 return 1000 + weight;
1373 // ouch, apply a huge penalty
1374 return 1000000 + weight;
1375 #else
1376 // convert to FontConfig values so that each style property fits within 8 bits
1377 int weight = abs (fc_weight (actual->weight) - fc_weight (desired->weight));
1378 int width = abs (fc_width (actual->width) - fc_width (desired->width));
1379 int slant = abs (fc_slant (actual->slant) - fc_slant (desired->slant));
1381 // weight has the highest priority, followed by weight and then slant
1382 return ((width & 0xff) << 16) | ((weight & 0xff) << 8) | (slant & 0xff);
1383 #endif
1386 static void
1387 canon_font_family_and_style (FontStyleInfo *desired, const char *family, FontStretches stretch, FontWeights weight, FontStyles style)
1389 desired->width = FontStretchesNormal;
1390 desired->weight = FontWeightsNormal;
1391 desired->slant = FontStylesNormal;
1392 desired->family_name = NULL;
1393 desired->set = 0;
1395 // extract whatever little style info we can from the family name
1396 style_info_parse (family, desired, true);
1398 // override style with user-specified attributes
1399 if (!(desired->set & Width))
1400 desired->width = stretch;
1401 if (!(desired->set & Weight))
1402 desired->weight = weight;
1403 if (!(desired->set & Slant))
1404 desired->slant = style;
1407 static FaceInfo *
1408 IndexMatchFace (FontIndex *index, const char *family, FontStretches stretch, FontWeights weight, FontStyles style)
1410 FontFile *file = (FontFile *) index->fonts->First ();
1411 FaceInfo *face, *best = NULL;
1412 FontStyleInfo desired;
1413 int closest = G_MAXINT;
1414 int diff;
1415 guint i;
1417 LOG_FONT (stderr, " * searching index for %s; %s\n", family, style_info_to_string (stretch, weight, style));
1419 canon_font_family_and_style (&desired, family, stretch, weight, style);
1421 LOG_FONT (stderr, " * canonicalized family/style: %s; %s\n", desired.family_name,
1422 style_info_to_string (desired.width, desired.weight, desired.slant));
1424 while (file != NULL) {
1425 for (i = 0; i < file->faces->len; i++) {
1426 face = (FaceInfo *) file->faces->pdata[i];
1428 if (!g_ascii_strcasecmp (face->family_name, desired.family_name)) {
1429 diff = style_diff (&face->style, &desired);
1430 if (diff < closest) {
1431 closest = diff;
1432 best = face;
1437 file = (FontFile *) file->next;
1440 g_free (desired.family_name);
1442 return best;
1445 FontFace *
1446 FontManager::OpenFontFace (const char *filename, const char *guid, int index)
1448 FT_Open_Args args;
1449 FT_Stream stream;
1450 FontFace *ff;
1451 FT_Face face;
1452 char *key;
1454 key = g_strdup_printf ("%s#%d", filename, index);
1455 if ((ff = (FontFace *) g_hash_table_lookup (faces, key))) {
1456 g_free (key);
1457 ff->ref ();
1458 return ff;
1461 if (!(stream = font_stream_new (filename, guid))) {
1462 g_free (key);
1463 return NULL;
1466 args.flags = FT_OPEN_STREAM;
1467 args.stream = stream;
1469 if (FT_Open_Face (libft2, &args, index, &face) != 0) {
1470 font_stream_destroy (stream);
1471 g_free (key);
1472 return NULL;
1475 if (!FT_IS_SCALABLE (face)) {
1476 FT_Done_Face (face);
1477 font_stream_destroy (stream);
1478 g_free (key);
1479 return NULL;
1482 return new FontFace (this, face, key);
1485 FontFace *
1486 FontManager::OpenFontResource (const char *resource, const char *family, int idx, FontStretches stretch, FontWeights weight, FontStyles style)
1488 FontIndex *index;
1489 FontFile *file;
1490 FontFace *face;
1491 FaceInfo *fi;
1493 LOG_FONT (stderr, "OpenFontResource (\"%s\", \"%s\", %d, %s)\n", resource ? resource : "(null)",
1494 family ? family : "(null)", idx, style_info_to_string (stretch, weight, style));
1496 if (!(index = (FontIndex *) g_hash_table_lookup (resources, resource))) {
1497 LOG_FONT (stderr, " * error: no such resource\n");
1498 return NULL;
1501 if (family != NULL) {
1502 // open by family
1503 if (!(fi = IndexMatchFace (index, family, stretch, weight, style))) {
1504 LOG_FONT (stderr, " * error: resource does not contain requested font\n");
1505 return NULL;
1507 } else if (idx >= 0) {
1508 // open by index
1509 if (!(file = (FontFile *) index->fonts->First ()) || file->next != NULL)
1510 return NULL;
1512 if ((int) file->faces->len <= idx)
1513 return NULL;
1515 fi = (FaceInfo *) file->faces->pdata[idx];
1516 } else {
1517 // no family or index specified... error?
1518 return NULL;
1521 if (!(face = OpenFontFace (fi->file->path, fi->file->guid, fi->index)))
1522 return NULL;
1524 LOG_FONT (stderr, " * opened %s; %s\n", face->GetFamilyName (), face->GetStyleName ());
1526 return face;
1529 FontFace *
1530 FontManager::OpenSystemFont (const char *family, FontStretches stretch, FontWeights weight, FontStyles style)
1532 FcPattern *pattern, *matched;
1533 FontStyleInfo desired;
1534 FcChar8 *filename;
1535 FcResult result;
1536 FontFace *face;
1537 int index;
1539 for (int attempt = 0; attempt < 2; attempt++) {
1540 if (attempt == 0) {
1541 desired.family_name = g_strdup (family);
1542 desired.width = stretch;
1543 desired.weight = weight;
1544 desired.slant = style;
1545 } else {
1546 g_free (desired.family_name);
1547 canon_font_family_and_style (&desired, family, stretch, weight, style);
1550 LOG_FONT (stderr, "Attempting to load installed font: %s %s... ", desired.family_name,
1551 style_info_to_string (desired.width, desired.weight, desired.slant));
1553 pattern = FcPatternCreate ();
1554 FcPatternAddDouble (pattern, FC_DPI, dpi);
1555 FcPatternAddString (pattern, FC_FAMILY, (const FcChar8 *) desired.family_name);
1556 FcPatternAddInteger (pattern, FC_WIDTH, fc_width (desired.width));
1557 FcPatternAddInteger (pattern, FC_WEIGHT, fc_weight (desired.weight));
1558 FcPatternAddInteger (pattern, FC_SLANT, fc_slant (desired.slant));
1559 FcDefaultSubstitute (pattern);
1561 if (!(matched = FcFontMatch (NULL, pattern, &result))) {
1562 LOG_FONT (stderr, "no matches\n");
1563 FcPatternDestroy (pattern);
1564 continue;
1567 FcPatternDestroy (pattern);
1569 if (FcPatternGetString (matched, FC_FILE, 0, &filename) != FcResultMatch) {
1570 LOG_FONT (stderr, "no filename\n");
1571 FcPatternDestroy (matched);
1572 continue;
1575 if (FcPatternGetInteger (matched, FC_INDEX, 0, &index) != FcResultMatch) {
1576 LOG_FONT (stderr, "no index\n");
1577 FcPatternDestroy (matched);
1578 continue;
1581 if ((face = OpenFontFace ((const char *) filename, NULL, index))) {
1582 if (!g_ascii_strcasecmp (face->GetFamilyName (), desired.family_name)) {
1583 LOG_FONT (stderr, "got %s %s\n", face->GetFamilyName (), face->GetStyleName ());
1584 g_free (desired.family_name);
1585 FcPatternDestroy (matched);
1586 return face;
1589 LOG_FONT (stderr, "family mismatch\n");
1590 face->unref ();
1591 } else {
1592 LOG_FONT (stderr, "family not found\n");
1595 FcPatternDestroy (matched);
1598 g_free (desired.family_name);
1600 return NULL;
1603 FontFace *
1604 FontManager::OpenFont (const char *name, FontStretches stretch, FontWeights weight, FontStyles style)
1606 const char *family;
1607 FontFace *face;
1608 //char *end;
1609 //int index;
1611 if ((family = strchr (name, '#'))) {
1612 char *resource = g_strndup (name, family - name);
1614 family++;
1616 //if ((index = strtol (family, &end, 10)) >= 0 && index < G_MAXINT && *end == '\0')
1617 // face = OpenFontResource (resource, NULL, index, stretch, weight, style);
1618 //else
1619 face = OpenFontResource (resource, family, -1, stretch, weight, style);
1621 g_free (resource);
1622 } else {
1623 face = OpenSystemFont (name, stretch, weight, style);
1626 return face;
1629 FontFace *
1630 FontManager::OpenFont (const char *name, int index)
1632 return OpenFontResource (name, NULL, index, FontStretchesNormal, FontWeightsNormal, FontStylesNormal);