register_font.cc 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. #include "register_font.h"
  2. #include <string>
  3. #include <pango/pangocairo.h>
  4. #include <pango/pango-fontmap.h>
  5. #include <pango/pango.h>
  6. #ifdef __APPLE__
  7. #include <CoreText/CoreText.h>
  8. #elif defined(_WIN32)
  9. #include <windows.h>
  10. #include <memory>
  11. #else
  12. #include <fontconfig/fontconfig.h>
  13. #include <pango/pangofc-fontmap.h>
  14. #endif
  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. // OSX seems to read the strings in MacRoman encoding and ignore Unicode entries.
  24. // You can verify this by opening a TTF with both Unicode and Macroman on OSX.
  25. // It uses the MacRoman name, while Fontconfig and Windows use Unicode
  26. #ifdef __APPLE__
  27. #define PREFERRED_PLATFORM_ID TT_PLATFORM_MACINTOSH
  28. #define PREFERRED_ENCODING_ID TT_MAC_ID_ROMAN
  29. #else
  30. #define PREFERRED_PLATFORM_ID TT_PLATFORM_MICROSOFT
  31. #define PREFERRED_ENCODING_ID TT_MS_ID_UNICODE_CS
  32. #endif
  33. // With PangoFcFontMaps (the pango font module on Linux) we're able to add a
  34. // hook that lets us get perfect matching. Tie the conditions for enabling that
  35. // feature to one variable
  36. #if !defined(__APPLE__) && !defined(_WIN32) && PANGO_VERSION_CHECK(1, 47, 0)
  37. #define PERFECT_MATCHES_ENABLED
  38. #endif
  39. #define IS_PREFERRED_ENC(X) \
  40. X.platform_id == PREFERRED_PLATFORM_ID && X.encoding_id == PREFERRED_ENCODING_ID
  41. #ifdef PERFECT_MATCHES_ENABLED
  42. // On Linux-like OSes using FontConfig, the PostScript name ranks higher than
  43. // preferred family and family name since we'll use it to get perfect font
  44. // matching (see fc_font_map_substitute_hook)
  45. #define GET_NAME_RANK(X) \
  46. ((IS_PREFERRED_ENC(X) ? 1 : 0) << 2) | \
  47. ((X.name_id == TT_NAME_ID_PS_NAME ? 1 : 0) << 1) | \
  48. (X.name_id == TT_NAME_ID_PREFERRED_FAMILY ? 1 : 0)
  49. #else
  50. #define GET_NAME_RANK(X) \
  51. ((IS_PREFERRED_ENC(X) ? 1 : 0) << 1) | \
  52. (X.name_id == TT_NAME_ID_PREFERRED_FAMILY ? 1 : 0)
  53. #endif
  54. /*
  55. * Return a UTF-8 encoded string given a TrueType name buf+len
  56. * and its platform and encoding
  57. */
  58. char *
  59. to_utf8(FT_Byte* buf, FT_UInt len, FT_UShort pid, FT_UShort eid) {
  60. size_t ret_len = len * 4; // max chars in a utf8 string
  61. char *ret = (char*)malloc(ret_len + 1); // utf8 string + null
  62. if (!ret) return NULL;
  63. // In my testing of hundreds of fonts from the Google Font repo, the two types
  64. // of fonts are TT_PLATFORM_MICROSOFT with TT_MS_ID_UNICODE_CS encoding, or
  65. // TT_PLATFORM_MACINTOSH with TT_MAC_ID_ROMAN encoding. Usually both, never neither
  66. char const *fromcode;
  67. if (pid == TT_PLATFORM_MACINTOSH && eid == TT_MAC_ID_ROMAN) {
  68. fromcode = "MAC";
  69. } else if (pid == TT_PLATFORM_MICROSOFT && eid == TT_MS_ID_UNICODE_CS) {
  70. fromcode = "UTF-16BE";
  71. } else {
  72. free(ret);
  73. return NULL;
  74. }
  75. GIConv cd = g_iconv_open("UTF-8", fromcode);
  76. if (cd == (GIConv)-1) {
  77. free(ret);
  78. return NULL;
  79. }
  80. size_t inbytesleft = len;
  81. size_t outbytesleft = ret_len;
  82. size_t n_converted = g_iconv(cd, (char**)&buf, &inbytesleft, &ret, &outbytesleft);
  83. ret -= ret_len - outbytesleft; // rewind the pointers to their
  84. buf -= len - inbytesleft; // original starting positions
  85. if (n_converted == (size_t)-1) {
  86. free(ret);
  87. return NULL;
  88. } else {
  89. ret[ret_len - outbytesleft] = '\0';
  90. return ret;
  91. }
  92. }
  93. /*
  94. * Find a family name in the face's name table, preferring the one the
  95. * system, fall back to the other
  96. */
  97. char *
  98. get_family_name(FT_Face face) {
  99. FT_SfntName name;
  100. int best_rank = -1;
  101. char* best_buf = NULL;
  102. for (unsigned i = 0; i < FT_Get_Sfnt_Name_Count(face); ++i) {
  103. FT_Get_Sfnt_Name(face, i, &name);
  104. if (
  105. name.name_id == TT_NAME_ID_FONT_FAMILY ||
  106. #ifdef PERFECT_MATCHES_ENABLED
  107. name.name_id == TT_NAME_ID_PS_NAME ||
  108. #endif
  109. name.name_id == TT_NAME_ID_PREFERRED_FAMILY
  110. ) {
  111. int rank = GET_NAME_RANK(name);
  112. if (rank > best_rank) {
  113. char *buf = to_utf8(name.string, name.string_len, name.platform_id, name.encoding_id);
  114. if (buf) {
  115. best_rank = rank;
  116. if (best_buf) free(best_buf);
  117. best_buf = buf;
  118. #ifdef PERFECT_MATCHES_ENABLED
  119. // Prepend an '@' to the postscript name
  120. if (name.name_id == TT_NAME_ID_PS_NAME) {
  121. std::string best_buf_modified = "@";
  122. best_buf_modified += best_buf;
  123. free(best_buf);
  124. best_buf = strdup(best_buf_modified.c_str());
  125. }
  126. #endif
  127. } else {
  128. free(buf);
  129. }
  130. }
  131. }
  132. }
  133. return best_buf;
  134. }
  135. PangoWeight
  136. get_pango_weight(FT_UShort weight) {
  137. switch (weight) {
  138. case 100: return PANGO_WEIGHT_THIN;
  139. case 200: return PANGO_WEIGHT_ULTRALIGHT;
  140. case 300: return PANGO_WEIGHT_LIGHT;
  141. #if PANGO_VERSION >= PANGO_VERSION_ENCODE(1, 36, 7)
  142. case 350: return PANGO_WEIGHT_SEMILIGHT;
  143. #endif
  144. case 380: return PANGO_WEIGHT_BOOK;
  145. case 400: return PANGO_WEIGHT_NORMAL;
  146. case 500: return PANGO_WEIGHT_MEDIUM;
  147. case 600: return PANGO_WEIGHT_SEMIBOLD;
  148. case 700: return PANGO_WEIGHT_BOLD;
  149. case 800: return PANGO_WEIGHT_ULTRABOLD;
  150. case 900: return PANGO_WEIGHT_HEAVY;
  151. case 1000: return PANGO_WEIGHT_ULTRAHEAVY;
  152. default: return PANGO_WEIGHT_NORMAL;
  153. }
  154. }
  155. PangoStretch
  156. get_pango_stretch(FT_UShort width) {
  157. switch (width) {
  158. case 1: return PANGO_STRETCH_ULTRA_CONDENSED;
  159. case 2: return PANGO_STRETCH_EXTRA_CONDENSED;
  160. case 3: return PANGO_STRETCH_CONDENSED;
  161. case 4: return PANGO_STRETCH_SEMI_CONDENSED;
  162. case 5: return PANGO_STRETCH_NORMAL;
  163. case 6: return PANGO_STRETCH_SEMI_EXPANDED;
  164. case 7: return PANGO_STRETCH_EXPANDED;
  165. case 8: return PANGO_STRETCH_EXTRA_EXPANDED;
  166. case 9: return PANGO_STRETCH_ULTRA_EXPANDED;
  167. default: return PANGO_STRETCH_NORMAL;
  168. }
  169. }
  170. PangoStyle
  171. get_pango_style(FT_Long flags) {
  172. if (flags & FT_STYLE_FLAG_ITALIC) {
  173. return PANGO_STYLE_ITALIC;
  174. } else {
  175. return PANGO_STYLE_NORMAL;
  176. }
  177. }
  178. #ifdef _WIN32
  179. std::unique_ptr<wchar_t[]>
  180. u8ToWide(const char* str) {
  181. int iBufferSize = MultiByteToWideChar(CP_UTF8, 0, str, -1, (wchar_t*)NULL, 0);
  182. if(!iBufferSize){
  183. return nullptr;
  184. }
  185. std::unique_ptr<wchar_t[]> wpBufWString = std::unique_ptr<wchar_t[]>{ new wchar_t[static_cast<size_t>(iBufferSize)] };
  186. if(!MultiByteToWideChar(CP_UTF8, 0, str, -1, wpBufWString.get(), iBufferSize)){
  187. return nullptr;
  188. }
  189. return wpBufWString;
  190. }
  191. static unsigned long
  192. stream_read_func(FT_Stream stream, unsigned long offset, unsigned char* buffer, unsigned long count){
  193. HANDLE hFile = reinterpret_cast<HANDLE>(stream->descriptor.pointer);
  194. DWORD numberOfBytesRead;
  195. OVERLAPPED overlapped;
  196. overlapped.Offset = offset;
  197. overlapped.OffsetHigh = 0;
  198. overlapped.hEvent = NULL;
  199. if(!ReadFile(hFile, buffer, count, &numberOfBytesRead, &overlapped)){
  200. return 0;
  201. }
  202. return numberOfBytesRead;
  203. };
  204. static void
  205. stream_close_func(FT_Stream stream){
  206. HANDLE hFile = reinterpret_cast<HANDLE>(stream->descriptor.pointer);
  207. CloseHandle(hFile);
  208. }
  209. #endif
  210. /*
  211. * Return a PangoFontDescription that will resolve to the font file
  212. */
  213. PangoFontDescription *
  214. get_pango_font_description(unsigned char* filepath) {
  215. FT_Library library;
  216. FT_Face face;
  217. PangoFontDescription *desc = pango_font_description_new();
  218. #ifdef _WIN32
  219. // FT_New_Face use fopen.
  220. // Unable to find the file when supplied the multibyte string path on the Windows platform and throw error "Could not parse font file".
  221. // This workaround fixes this by reading the font file uses win32 wide character API.
  222. std::unique_ptr<wchar_t[]> wFilepath = u8ToWide((char*)filepath);
  223. if(!wFilepath){
  224. return NULL;
  225. }
  226. HANDLE hFile = CreateFileW(
  227. wFilepath.get(),
  228. GENERIC_READ,
  229. FILE_SHARE_READ,
  230. NULL,
  231. OPEN_EXISTING,
  232. NULL,
  233. NULL
  234. );
  235. if(!hFile){
  236. return NULL;
  237. }
  238. LARGE_INTEGER liSize;
  239. if(!GetFileSizeEx(hFile, &liSize)) {
  240. CloseHandle(hFile);
  241. return NULL;
  242. }
  243. FT_Open_Args args;
  244. args.flags = FT_OPEN_STREAM;
  245. FT_StreamRec stream;
  246. stream.base = NULL;
  247. stream.size = liSize.QuadPart;
  248. stream.pos = 0;
  249. stream.descriptor.pointer = hFile;
  250. stream.read = stream_read_func;
  251. stream.close = stream_close_func;
  252. args.stream = &stream;
  253. if (
  254. !FT_Init_FreeType(&library) &&
  255. !FT_Open_Face(library, &args, 0, &face)) {
  256. #else
  257. if (!FT_Init_FreeType(&library) && !FT_New_Face(library, (const char*)filepath, 0, &face)) {
  258. #endif
  259. TT_OS2 *table = (TT_OS2*)FT_Get_Sfnt_Table(face, FT_SFNT_OS2);
  260. if (table) {
  261. char *family = get_family_name(face);
  262. if (!family) {
  263. pango_font_description_free(desc);
  264. FT_Done_Face(face);
  265. FT_Done_FreeType(library);
  266. return NULL;
  267. }
  268. pango_font_description_set_family_static(desc, family);
  269. pango_font_description_set_weight(desc, get_pango_weight(table->usWeightClass));
  270. pango_font_description_set_stretch(desc, get_pango_stretch(table->usWidthClass));
  271. pango_font_description_set_style(desc, get_pango_style(face->style_flags));
  272. FT_Done_Face(face);
  273. FT_Done_FreeType(library);
  274. return desc;
  275. }
  276. }
  277. pango_font_description_free(desc);
  278. return NULL;
  279. }
  280. #ifdef PERFECT_MATCHES_ENABLED
  281. static void
  282. fc_font_map_substitute_hook(FcPattern *pat, gpointer data) {
  283. FcChar8 *family;
  284. for (int i = 0; FcPatternGetString(pat, FC_FAMILY, i, &family) == FcResultMatch; i++) {
  285. if (family[0] == '@') {
  286. FcPatternAddString(pat, FC_POSTSCRIPT_NAME, (FcChar8 *)family + 1);
  287. FcPatternRemove(pat, FC_FAMILY, i);
  288. i -= 1;
  289. }
  290. }
  291. }
  292. #endif
  293. /*
  294. * Register font with the OS
  295. */
  296. bool
  297. register_font(unsigned char *filepath) {
  298. bool success;
  299. #ifdef __APPLE__
  300. CFURLRef filepathUrl = CFURLCreateFromFileSystemRepresentation(NULL, filepath, strlen((char*)filepath), false);
  301. success = CTFontManagerRegisterFontsForURL(filepathUrl, kCTFontManagerScopeProcess, NULL);
  302. #elif defined(_WIN32)
  303. std::unique_ptr<wchar_t[]> wFilepath = u8ToWide((char*)filepath);
  304. if(wFilepath){
  305. success = AddFontResourceExW(wFilepath.get(), FR_PRIVATE, 0) != 0;
  306. }else{
  307. success = false;
  308. }
  309. #else
  310. success = FcConfigAppFontAddFile(FcConfigGetCurrent(), (FcChar8 *)(filepath));
  311. #endif
  312. if (!success) return false;
  313. // Tell Pango to throw away the current FontMap and create a new one. This
  314. // has the effect of registering the new font in Pango by re-looking up all
  315. // font families.
  316. pango_cairo_font_map_set_default(NULL);
  317. #ifdef PERFECT_MATCHES_ENABLED
  318. PangoFontMap* map = pango_cairo_font_map_get_default();
  319. PangoFcFontMap* fc_map = PANGO_FC_FONT_MAP(map);
  320. pango_fc_font_map_set_default_substitute(fc_map, fc_font_map_substitute_hook, NULL, NULL);
  321. #endif
  322. return true;
  323. }
  324. /*
  325. * Deregister font from the OS
  326. * Note that Linux (FontConfig) can only dereregister ALL fonts at once.
  327. */
  328. bool
  329. deregister_font(unsigned char *filepath) {
  330. bool success;
  331. #ifdef __APPLE__
  332. CFURLRef filepathUrl = CFURLCreateFromFileSystemRepresentation(NULL, filepath, strlen((char*)filepath), false);
  333. success = CTFontManagerUnregisterFontsForURL(filepathUrl, kCTFontManagerScopeProcess, NULL);
  334. #elif defined(_WIN32)
  335. std::unique_ptr<wchar_t[]> wFilepath = u8ToWide((char*)filepath);
  336. if(wFilepath){
  337. success = RemoveFontResourceExW(wFilepath.get(), FR_PRIVATE, 0) != 0;
  338. }else{
  339. success = false;
  340. }
  341. #else
  342. FcConfigAppFontClear(FcConfigGetCurrent());
  343. success = true;
  344. #endif
  345. if (!success) return false;
  346. // Tell Pango to throw away the current FontMap and create a new one. This
  347. // has the effect of deregistering the font in Pango by re-looking up all
  348. // font families.
  349. pango_cairo_font_map_set_default(NULL);
  350. return true;
  351. }