#include #include #include #ifdef __APPLE__ #include #elif defined(_WIN32) #include #else #include #endif #include #include FT_FREETYPE_H #include FT_TRUETYPE_TABLES_H #include FT_SFNT_NAMES_H #include FT_TRUETYPE_IDS_H #ifndef FT_SFNT_OS2 #define FT_SFNT_OS2 ft_sfnt_os2 #endif // OSX seems to read the strings in MacRoman encoding and ignore Unicode entries. // You can verify this by opening a TTF with both Unicode and Macroman on OSX. // It uses the MacRoman name, while Fontconfig and Windows use Unicode #ifdef __APPLE__ #define PREFERRED_PLATFORM_ID TT_PLATFORM_MACINTOSH #define PREFERRED_ENCODING_ID TT_MAC_ID_ROMAN #else #define PREFERRED_PLATFORM_ID TT_PLATFORM_MICROSOFT #define PREFERRED_ENCODING_ID TT_MS_ID_UNICODE_CS #endif #define IS_PREFERRED_ENC(X) \ X.platform_id == PREFERRED_PLATFORM_ID && X.encoding_id == PREFERRED_ENCODING_ID #define GET_NAME_RANK(X) \ (IS_PREFERRED_ENC(X) ? 1 : 0) + (X.name_id == TT_NAME_ID_PREFERRED_FAMILY ? 1 : 0) /* * Return a UTF-8 encoded string given a TrueType name buf+len * and its platform and encoding */ char * to_utf8(FT_Byte* buf, FT_UInt len, FT_UShort pid, FT_UShort eid) { size_t ret_len = len * 4; // max chars in a utf8 string char *ret = (char*)malloc(ret_len + 1); // utf8 string + null if (!ret) return NULL; // In my testing of hundreds of fonts from the Google Font repo, the two types // of fonts are TT_PLATFORM_MICROSOFT with TT_MS_ID_UNICODE_CS encoding, or // TT_PLATFORM_MACINTOSH with TT_MAC_ID_ROMAN encoding. Usually both, never neither char const *fromcode; if (pid == TT_PLATFORM_MACINTOSH && eid == TT_MAC_ID_ROMAN) { fromcode = "MAC"; } else if (pid == TT_PLATFORM_MICROSOFT && eid == TT_MS_ID_UNICODE_CS) { fromcode = "UTF-16BE"; } else { free(ret); return NULL; } GIConv cd = g_iconv_open("UTF-8", fromcode); if (cd == (GIConv)-1) { free(ret); return NULL; } size_t inbytesleft = len; size_t outbytesleft = ret_len; size_t n_converted = g_iconv(cd, (char**)&buf, &inbytesleft, &ret, &outbytesleft); ret -= ret_len - outbytesleft; // rewind the pointers to their buf -= len - inbytesleft; // original starting positions if (n_converted == (size_t)-1) { free(ret); return NULL; } else { ret[ret_len - outbytesleft] = '\0'; return ret; } } /* * Find a family name in the face's name table, preferring the one the * system, fall back to the other */ typedef struct _NameDef { const char *buf; int rank; // the higher the more desirable } NameDef; gint _name_def_compare(gconstpointer a, gconstpointer b) { return ((NameDef*)a)->rank > ((NameDef*)b)->rank ? -1 : 1; } // Some versions of GTK+ do not have this, particualrly the one we // currently link to in node-canvas's wiki void _free_g_list_item(gpointer data, gpointer user_data) { NameDef *d = (NameDef *)data; free((void *)(d->buf)); } void _g_list_free_full(GList *list) { g_list_foreach(list, _free_g_list_item, NULL); g_list_free(list); } char * get_family_name(FT_Face face) { FT_SfntName name; GList *list = NULL; char *utf8name = NULL; for (unsigned i = 0; i < FT_Get_Sfnt_Name_Count(face); ++i) { FT_Get_Sfnt_Name(face, i, &name); if (name.name_id == TT_NAME_ID_FONT_FAMILY || name.name_id == TT_NAME_ID_PREFERRED_FAMILY) { char *buf = to_utf8(name.string, name.string_len, name.platform_id, name.encoding_id); if (buf) { NameDef *d = (NameDef*)malloc(sizeof(NameDef)); d->buf = (const char*)buf; d->rank = GET_NAME_RANK(name); list = g_list_insert_sorted(list, (gpointer)d, _name_def_compare); } } } GList *best_def = g_list_first(list); if (best_def) utf8name = (char*) strdup(((NameDef*)best_def->data)->buf); if (list) _g_list_free_full(list); return utf8name; } PangoWeight get_pango_weight(FT_UShort weight) { switch (weight) { case 100: return PANGO_WEIGHT_THIN; case 200: return PANGO_WEIGHT_ULTRALIGHT; case 300: return PANGO_WEIGHT_LIGHT; #if PANGO_VERSION >= PANGO_VERSION_ENCODE(1, 36, 7) case 350: return PANGO_WEIGHT_SEMILIGHT; #endif case 380: return PANGO_WEIGHT_BOOK; case 400: return PANGO_WEIGHT_NORMAL; case 500: return PANGO_WEIGHT_MEDIUM; case 600: return PANGO_WEIGHT_SEMIBOLD; case 700: return PANGO_WEIGHT_BOLD; case 800: return PANGO_WEIGHT_ULTRABOLD; case 900: return PANGO_WEIGHT_HEAVY; case 1000: return PANGO_WEIGHT_ULTRAHEAVY; default: return PANGO_WEIGHT_NORMAL; } } PangoStretch get_pango_stretch(FT_UShort width) { switch (width) { case 1: return PANGO_STRETCH_ULTRA_CONDENSED; case 2: return PANGO_STRETCH_EXTRA_CONDENSED; case 3: return PANGO_STRETCH_CONDENSED; case 4: return PANGO_STRETCH_SEMI_CONDENSED; case 5: return PANGO_STRETCH_NORMAL; case 6: return PANGO_STRETCH_SEMI_EXPANDED; case 7: return PANGO_STRETCH_EXPANDED; case 8: return PANGO_STRETCH_EXTRA_EXPANDED; case 9: return PANGO_STRETCH_ULTRA_EXPANDED; default: return PANGO_STRETCH_NORMAL; } } PangoStyle get_pango_style(FT_Long flags) { if (flags & FT_STYLE_FLAG_ITALIC) { return PANGO_STYLE_ITALIC; } else { return PANGO_STYLE_NORMAL; } } PangoFontDescription * get_pango_font_description(unsigned char* filepath) { FT_Library library; FT_Face face; PangoFontDescription *desc = pango_font_description_new(); if (!FT_Init_FreeType(&library) && !FT_New_Face(library, (const char*)filepath, 0, &face)) { TT_OS2 *table = (TT_OS2*)FT_Get_Sfnt_Table(face, FT_SFNT_OS2); char *family = get_family_name(face); if (family) pango_font_description_set_family_static(desc, family); pango_font_description_set_weight(desc, get_pango_weight(table->usWeightClass)); pango_font_description_set_stretch(desc, get_pango_stretch(table->usWidthClass)); pango_font_description_set_style(desc, get_pango_style(face->style_flags)); FT_Done_Face(face); return desc; } pango_font_description_free(desc); return NULL; } /* * Register font with the OS */ bool register_font(unsigned char *filepath, PangoFontDescription **desc) { bool success; #ifdef __APPLE__ CFURLRef filepathUrl = CFURLCreateFromFileSystemRepresentation(NULL, filepath, strlen((char*)filepath), false); success = CTFontManagerRegisterFontsForURL(filepathUrl, kCTFontManagerScopeProcess, NULL); #elif defined(_WIN32) success = AddFontResourceEx((LPCSTR)filepath, FR_PRIVATE, 0) != 0; #else success = FcConfigAppFontAddFile(FcConfigGetCurrent(), (FcChar8 *)(filepath)); #endif if (!success) return false; *desc = get_pango_font_description(filepath); // Tell Pango to throw away the current FontMap and create a new one. This // has the effect of registering the new font in Pango by re-looking up all // font families. pango_cairo_font_map_set_default(NULL); return true; }