UNPKG

7.29 kBtext/x-cView Raw
1#include "register_font.h"
2
3#include <pango/pangocairo.h>
4#include <pango/pango-fontmap.h>
5#include <pango/pango.h>
6
7#ifdef __APPLE__
8#include <CoreText/CoreText.h>
9#elif defined(_WIN32)
10#include <windows.h>
11#else
12#include <fontconfig/fontconfig.h>
13#endif
14
15#include <ft2build.h>
16#include FT_FREETYPE_H
17#include FT_TRUETYPE_TABLES_H
18#include FT_SFNT_NAMES_H
19#include FT_TRUETYPE_IDS_H
20#ifndef FT_SFNT_OS2
21#define FT_SFNT_OS2 ft_sfnt_os2
22#endif
23
24// OSX seems to read the strings in MacRoman encoding and ignore Unicode entries.
25// You can verify this by opening a TTF with both Unicode and Macroman on OSX.
26// It uses the MacRoman name, while Fontconfig and Windows use Unicode
27#ifdef __APPLE__
28#define PREFERRED_PLATFORM_ID TT_PLATFORM_MACINTOSH
29#define PREFERRED_ENCODING_ID TT_MAC_ID_ROMAN
30#else
31#define PREFERRED_PLATFORM_ID TT_PLATFORM_MICROSOFT
32#define PREFERRED_ENCODING_ID TT_MS_ID_UNICODE_CS
33#endif
34
35#define IS_PREFERRED_ENC(X) \
36 X.platform_id == PREFERRED_PLATFORM_ID && X.encoding_id == PREFERRED_ENCODING_ID
37
38#define GET_NAME_RANK(X) \
39 (IS_PREFERRED_ENC(X) ? 1 : 0) + (X.name_id == TT_NAME_ID_PREFERRED_FAMILY ? 1 : 0)
40
41/*
42 * Return a UTF-8 encoded string given a TrueType name buf+len
43 * and its platform and encoding
44 */
45
46char *
47to_utf8(FT_Byte* buf, FT_UInt len, FT_UShort pid, FT_UShort eid) {
48 size_t ret_len = len * 4; // max chars in a utf8 string
49 char *ret = (char*)malloc(ret_len + 1); // utf8 string + null
50
51 if (!ret) return NULL;
52
53 // In my testing of hundreds of fonts from the Google Font repo, the two types
54 // of fonts are TT_PLATFORM_MICROSOFT with TT_MS_ID_UNICODE_CS encoding, or
55 // TT_PLATFORM_MACINTOSH with TT_MAC_ID_ROMAN encoding. Usually both, never neither
56
57 char const *fromcode;
58
59 if (pid == TT_PLATFORM_MACINTOSH && eid == TT_MAC_ID_ROMAN) {
60 fromcode = "MAC";
61 } else if (pid == TT_PLATFORM_MICROSOFT && eid == TT_MS_ID_UNICODE_CS) {
62 fromcode = "UTF-16BE";
63 } else {
64 free(ret);
65 return NULL;
66 }
67
68 GIConv cd = g_iconv_open("UTF-8", fromcode);
69
70 if (cd == (GIConv)-1) {
71 free(ret);
72 return NULL;
73 }
74
75 size_t inbytesleft = len;
76 size_t outbytesleft = ret_len;
77
78 size_t n_converted = g_iconv(cd, (char**)&buf, &inbytesleft, &ret, &outbytesleft);
79
80 ret -= ret_len - outbytesleft; // rewind the pointers to their
81 buf -= len - inbytesleft; // original starting positions
82
83 if (n_converted == (size_t)-1) {
84 free(ret);
85 return NULL;
86 } else {
87 ret[ret_len - outbytesleft] = '\0';
88 return ret;
89 }
90}
91
92/*
93 * Find a family name in the face's name table, preferring the one the
94 * system, fall back to the other
95 */
96
97typedef struct _NameDef {
98 const char *buf;
99 int rank; // the higher the more desirable
100} NameDef;
101
102gint
103_name_def_compare(gconstpointer a, gconstpointer b) {
104 return ((NameDef*)a)->rank > ((NameDef*)b)->rank ? -1 : 1;
105}
106
107// Some versions of GTK+ do not have this, particualrly the one we
108// currently link to in node-canvas's wiki
109void
110_free_g_list_item(gpointer data, gpointer user_data) {
111 NameDef *d = (NameDef *)data;
112 free((void *)(d->buf));
113}
114
115void
116_g_list_free_full(GList *list) {
117 g_list_foreach(list, _free_g_list_item, NULL);
118 g_list_free(list);
119}
120
121char *
122get_family_name(FT_Face face) {
123 FT_SfntName name;
124 GList *list = NULL;
125 char *utf8name = NULL;
126
127 for (unsigned i = 0; i < FT_Get_Sfnt_Name_Count(face); ++i) {
128 FT_Get_Sfnt_Name(face, i, &name);
129
130 if (name.name_id == TT_NAME_ID_FONT_FAMILY || name.name_id == TT_NAME_ID_PREFERRED_FAMILY) {
131 char *buf = to_utf8(name.string, name.string_len, name.platform_id, name.encoding_id);
132
133 if (buf) {
134 NameDef *d = (NameDef*)malloc(sizeof(NameDef));
135 d->buf = (const char*)buf;
136 d->rank = GET_NAME_RANK(name);
137
138 list = g_list_insert_sorted(list, (gpointer)d, _name_def_compare);
139 }
140 }
141 }
142
143 GList *best_def = g_list_first(list);
144 if (best_def) utf8name = (char*) strdup(((NameDef*)best_def->data)->buf);
145 if (list) _g_list_free_full(list);
146
147 return utf8name;
148}
149
150PangoWeight
151get_pango_weight(FT_UShort weight) {
152 switch (weight) {
153 case 100: return PANGO_WEIGHT_THIN;
154 case 200: return PANGO_WEIGHT_ULTRALIGHT;
155 case 300: return PANGO_WEIGHT_LIGHT;
156 #if PANGO_VERSION >= PANGO_VERSION_ENCODE(1, 36, 7)
157 case 350: return PANGO_WEIGHT_SEMILIGHT;
158 #endif
159 case 380: return PANGO_WEIGHT_BOOK;
160 case 400: return PANGO_WEIGHT_NORMAL;
161 case 500: return PANGO_WEIGHT_MEDIUM;
162 case 600: return PANGO_WEIGHT_SEMIBOLD;
163 case 700: return PANGO_WEIGHT_BOLD;
164 case 800: return PANGO_WEIGHT_ULTRABOLD;
165 case 900: return PANGO_WEIGHT_HEAVY;
166 case 1000: return PANGO_WEIGHT_ULTRAHEAVY;
167 default: return PANGO_WEIGHT_NORMAL;
168 }
169}
170
171PangoStretch
172get_pango_stretch(FT_UShort width) {
173 switch (width) {
174 case 1: return PANGO_STRETCH_ULTRA_CONDENSED;
175 case 2: return PANGO_STRETCH_EXTRA_CONDENSED;
176 case 3: return PANGO_STRETCH_CONDENSED;
177 case 4: return PANGO_STRETCH_SEMI_CONDENSED;
178 case 5: return PANGO_STRETCH_NORMAL;
179 case 6: return PANGO_STRETCH_SEMI_EXPANDED;
180 case 7: return PANGO_STRETCH_EXPANDED;
181 case 8: return PANGO_STRETCH_EXTRA_EXPANDED;
182 case 9: return PANGO_STRETCH_ULTRA_EXPANDED;
183 default: return PANGO_STRETCH_NORMAL;
184 }
185}
186
187PangoStyle
188get_pango_style(FT_Long flags) {
189 if (flags & FT_STYLE_FLAG_ITALIC) {
190 return PANGO_STYLE_ITALIC;
191 } else {
192 return PANGO_STYLE_NORMAL;
193 }
194}
195
196/*
197 * Return a PangoFontDescription that will resolve to the font file
198 */
199
200PangoFontDescription *
201get_pango_font_description(unsigned char* filepath) {
202 FT_Library library;
203 FT_Face face;
204 PangoFontDescription *desc = pango_font_description_new();
205
206 if (!FT_Init_FreeType(&library) && !FT_New_Face(library, (const char*)filepath, 0, &face)) {
207 TT_OS2 *table = (TT_OS2*)FT_Get_Sfnt_Table(face, FT_SFNT_OS2);
208 if (table) {
209 char *family = get_family_name(face);
210
211 if (!family) {
212 pango_font_description_free(desc);
213 FT_Done_Face(face);
214 FT_Done_FreeType(library);
215
216 return NULL;
217 }
218
219 pango_font_description_set_family_static(desc, family);
220 pango_font_description_set_weight(desc, get_pango_weight(table->usWeightClass));
221 pango_font_description_set_stretch(desc, get_pango_stretch(table->usWidthClass));
222 pango_font_description_set_style(desc, get_pango_style(face->style_flags));
223
224 FT_Done_Face(face);
225 FT_Done_FreeType(library);
226
227 return desc;
228 }
229 }
230
231 pango_font_description_free(desc);
232
233 return NULL;
234}
235
236/*
237 * Register font with the OS
238 */
239
240bool
241register_font(unsigned char *filepath) {
242 bool success;
243
244 #ifdef __APPLE__
245 CFURLRef filepathUrl = CFURLCreateFromFileSystemRepresentation(NULL, filepath, strlen((char*)filepath), false);
246 success = CTFontManagerRegisterFontsForURL(filepathUrl, kCTFontManagerScopeProcess, NULL);
247 #elif defined(_WIN32)
248 success = AddFontResourceEx((LPCSTR)filepath, FR_PRIVATE, 0) != 0;
249 #else
250 success = FcConfigAppFontAddFile(FcConfigGetCurrent(), (FcChar8 *)(filepath));
251 #endif
252
253 if (!success) return false;
254
255 // Tell Pango to throw away the current FontMap and create a new one. This
256 // has the effect of registering the new font in Pango by re-looking up all
257 // font families.
258 pango_cairo_font_map_set_default(NULL);
259
260 return true;
261}
262