From 7f0ae838d71de5de3f2cc030e8c95bb24b0436cd Mon Sep 17 00:00:00 2001 From: jp9000 Date: Sun, 17 Aug 2014 07:13:05 -0700 Subject: [PATCH] Add system font lookup for mac/windows This changes the font plugin from using a font file to using a specific installed system font, which is searched for on each specific system and associated with the font file. It now uses a font property instead of a path property, and font size has been removed because the font property now handles that. When the module is first loaded, it will build up a list of system fonts in order to be usable by Freetype. It was quite painful to program this because font files can contain multiple localized versions of their face names, and then there was the issue where windows likes to mangle custom style types to the font name. Regardless, it all seems to have worked out pretty well. Minor issues: - Truetype/Opentype fonts sometimes do not automatically have italic and/or bold styles available, it seems that the system applies transformations manually in those cases. We don't do this yet, however, so right now a user might select a font with italic/bold only to discover that italic/bold doesn't always work. Not entirely sure what to do about this yet. There's probably a freetype function to do something like that somehow, This also requires that iconv be used for non-windows systems to be able to look up localized font names within font files. Windows will use the win32 API and code page IDs to translate font names. --- cmake/Modules/Findiconv.cmake | 56 +++++ plugins/text-freetype2/CMakeLists.txt | 67 ++++- plugins/text-freetype2/data/locale/en-US.ini | 5 +- plugins/text-freetype2/find-font-cocoa.m | 58 +++++ plugins/text-freetype2/find-font-iconv.c | 154 ++++++++++++ plugins/text-freetype2/find-font-windows.c | 248 +++++++++++++++++++ plugins/text-freetype2/find-font.c | 202 +++++++++++++++ plugins/text-freetype2/find-font.h | 40 +++ plugins/text-freetype2/text-freetype2.c | 107 +++++--- plugins/text-freetype2/text-freetype2.h | 8 +- 10 files changed, 891 insertions(+), 54 deletions(-) create mode 100644 cmake/Modules/Findiconv.cmake create mode 100644 plugins/text-freetype2/find-font-cocoa.m create mode 100644 plugins/text-freetype2/find-font-iconv.c create mode 100644 plugins/text-freetype2/find-font-windows.c create mode 100644 plugins/text-freetype2/find-font.c create mode 100644 plugins/text-freetype2/find-font.h diff --git a/cmake/Modules/Findiconv.cmake b/cmake/Modules/Findiconv.cmake new file mode 100644 index 000000000..b39544f27 --- /dev/null +++ b/cmake/Modules/Findiconv.cmake @@ -0,0 +1,56 @@ +# Once done these will be defined: +# +# LIBICONV_FOUND +# LIBICONV_INCLUDE_DIRS +# LIBICONV_LIBRARIES +# +# For use in OBS: +# +# ICONV_INCLUDE_DIR +# + +if(LIBICONV_INCLUDE_DIRS AND LIBICONV_LIBRARIES) + set(LIBICONV_FOUND TRUE) +else() + find_package(PkgConfig QUIET) + if (PKG_CONFIG_FOUND) + pkg_check_modules(_ICONV QUIET iconv) + endif() + + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(_lib_suffix 64) + else() + set(_lib_suffix 32) + endif() + + set(ICONV_PATH_ARCH IConvPath${_lib_suffix}) + + find_path(ICONV_INCLUDE_DIR + NAMES iconv.h + HINTS + ${_ICONV_INCLUDE_DIRS} + "${CMAKE_SOURCE_DIR}/additional_install_files/include" + "$ENV{obsAdditionalInstallFiles}/include" + ENV IConvPath + ENV ${ICONV_PATH_ARCH} + PATHS + /usr/include /usr/local/include /opt/local/include /sw/include) + + find_library(ICONV_LIB + NAMES ${_ICONV_LIBRARIES} iconv libiconv + HINTS + ${_ICONV_LIBRARY_DIRS} + "${ICONV_INCLUDE_DIR}/../lib" + "${ICONV_INCLUDE_DIR}/../lib${_lib_suffix}" + "${ICONV_INCLUDE_DIR}/../libs${_lib_suffix}" + "${ICONV_INCLUDE_DIR}/lib" + "${ICONV_INCLUDE_DIR}/lib${_lib_suffix}" + PATHS + /usr/lib /usr/local/lib /opt/local/lib /sw/lib) + + set(LIBICONV_INCLUDE_DIRS ${ICONV_INCLUDE_DIR} CACHE PATH "iconv include dir") + set(LIBICONV_LIBRARIES ${ICONV_LIB} CACHE STRING "iconv libraries") + + find_package_handle_standard_args(Libiconv DEFAULT_MSG ICONV_LIB ICONV_INCLUDE_DIR) + mark_as_advanced(ICONV_INCLUDE_DIR ICONV_LIB) +endif() diff --git a/plugins/text-freetype2/CMakeLists.txt b/plugins/text-freetype2/CMakeLists.txt index 4770e17cb..e6a4a9578 100644 --- a/plugins/text-freetype2/CMakeLists.txt +++ b/plugins/text-freetype2/CMakeLists.txt @@ -1,23 +1,72 @@ project(text-freetype2) - -find_package(Freetype REQUIRED) - -include_directories( - SYSTEM "${CMAKE_SOURCE_DIR}/libobs" - ${LIBFREETYPE_INCLUDE_DIRS}) - + +find_package(Freetype QUIET) +if(NOT LIBFREETYPE_FOUND) + message(STATUS "Freetype library not found, Freetype text plugin disabled") + return() +endif() + +if(APPLE) + find_package(iconv QUIET) + if(NOT LIBICONV_FOUND) + message(STATUS "IConv library not found, Freetype text plugin disabled") + return() + endif() + + find_library(COCOA Cocoa) + + set(text-freetype2_PLATFORM_SOURCES + find-font-cocoa.m + find-font-iconv.c) + + include_directories(${COCOA} + ${LIBICONV_INCLUDE_DIRS}) + + set(text-freetype2_PLATFORM_DEPS + ${COCOA} + ${LIBICONV_LIBRARIES}) + + set_source_files_properties(find-font-cocoa.m + PROPERTIES LANGUAGE C) +elseif(WIN32) + set(text-freetype2_PLATFORM_SOURCES + find-font-windows.c) +else() + message(STATUS "Linux-specific code has yet to be written for the text plugin, just needs load_os_font_list written.. which, er, may or may not be a pain. (My apologies in advance, please don't strangle me)") + return() + + find_package(ICONV QUIET) + if(NOT LIBICONV_FOUND) + message(STATUS "IConv library not found, Freetype text plugin disabled") + return() + endif() + + set(text-freetype2_PLATFORM_SOURCES + find-font-iconv.c) + + include_directories(${LIBICONV_INCLUDE_DIR}) + target_link_libraries(${LIBICONV_LIBRARIES}) +endif() + +include_directories(${LIBFREETYPE_INCLUDE_DIRS}) +add_definitions(${LIBFREETYPE_DEFINITIONS}) + set(text-freetype2_SOURCES + find-font.h + find-font.c obs-convenience.c text-functionality.c text-freetype2.c obs-convenience.h text-freetype2.h) - + add_library(text-freetype2 MODULE + ${text-freetype2_PLATFORM_SOURCES} ${text-freetype2_SOURCES}) target_link_libraries(text-freetype2 libobs + ${text-freetype2_PLATFORM_DEPS} ${LIBFREETYPE_LIBRARIES}) - + install_obs_plugin(text-freetype2) install_obs_plugin_data(text-freetype2 data) diff --git a/plugins/text-freetype2/data/locale/en-US.ini b/plugins/text-freetype2/data/locale/en-US.ini index b7e445cb5..ee011b165 100644 --- a/plugins/text-freetype2/data/locale/en-US.ini +++ b/plugins/text-freetype2/data/locale/en-US.ini @@ -1,12 +1,11 @@ -FontFile="Font File" +Font="Font" Text="Text" TextFile="Text File (UTF-8 or UTF-16)" ChatLogMode="Chat log mode (last 6 lines)" -FontSize="Font Size (Pixels)" Color1="Color 1" Color2="Color 2" Outline="Outline" DropShadow="Drop Shadow" ReadFromFile="Read from file" CustomWidth="Custom text width" -WordWrap="Word Wrap" \ No newline at end of file +WordWrap="Word Wrap" diff --git a/plugins/text-freetype2/find-font-cocoa.m b/plugins/text-freetype2/find-font-cocoa.m new file mode 100644 index 000000000..acd75f9b9 --- /dev/null +++ b/plugins/text-freetype2/find-font-cocoa.m @@ -0,0 +1,58 @@ +#include +#include "find-font.h" +#include "text-freetype2.h" + +#import + +static inline void add_path_font(const char *path) +{ + FT_Face face; + FT_Long idx = 0; + FT_Long max_faces = 1; + + while (idx < max_faces) { + if (FT_New_Face(ft2_lib, path, idx, &face) != 0) + break; + + build_font_path_info(face, idx++, path); + max_faces = face->num_faces; + FT_Done_Face(face); + } +} + +static void add_path_fonts(NSFileManager *file_manager, NSString *path) +{ + NSArray *files = NULL; + NSError *error = NULL; + + files = [file_manager contentsOfDirectoryAtPath:path error:&error]; + + for (NSString *file in files) { + NSString *full_path = [path stringByAppendingPathComponent:file]; + + add_path_font(full_path.fileSystemRepresentation); + } +} + +void load_os_font_list(void) +{ + @autoreleasepool { + BOOL is_dir; + NSArray *paths = NSSearchPathForDirectoriesInDomains( + NSLibraryDirectory, NSAllDomainsMask, true); + + for (NSString *path in paths) { + NSFileManager *file_manager = + [NSFileManager defaultManager]; + NSString *font_path = + [path stringByAppendingPathComponent:@"Fonts"]; + + bool folder_exists = [file_manager + fileExistsAtPath:font_path + isDirectory:&is_dir]; + + if (folder_exists && is_dir) + add_path_fonts(file_manager, font_path); + } + } +} diff --git a/plugins/text-freetype2/find-font-iconv.c b/plugins/text-freetype2/find-font-iconv.c new file mode 100644 index 000000000..9de7e4609 --- /dev/null +++ b/plugins/text-freetype2/find-font-iconv.c @@ -0,0 +1,154 @@ +#include +#include +#include "find-font.h" + +struct mac_font_mapping { + unsigned short encoding_id; + unsigned short language_id; + const char *code_page; +}; + +#define TT_MAC_LANGID_ANY 0xFFFF + +static const struct mac_font_mapping mac_codes[] = { + {TT_MAC_ID_ROMAN, TT_MAC_LANGID_ENGLISH, "macintosh"}, + {TT_MAC_ID_ROMAN, TT_MAC_LANGID_ICELANDIC,"x-mac-icelandic"}, + {TT_MAC_ID_ROMAN, TT_MAC_LANGID_TURKISH, "x-mac-ce"}, + {TT_MAC_ID_ROMAN, TT_MAC_LANGID_POLISH, "x-mac-ce"}, + {TT_MAC_ID_ROMAN, TT_MAC_LANGID_ROMANIAN, "x-mac-romanian"}, + {TT_MAC_ID_ROMAN, TT_MAC_LANGID_CZECH, "x-mac-ce"}, + {TT_MAC_ID_ROMAN, TT_MAC_LANGID_SLOVAK, "x-mac-ce"}, + {TT_MAC_ID_ROMAN, TT_MAC_LANGID_ANY, "macintosh"}, + {TT_MAC_ID_JAPANESE, TT_MAC_LANGID_JAPANESE, "Shift_JIS"}, + {TT_MAC_ID_JAPANESE, TT_MAC_LANGID_ANY, "Shift_JIS"}, + {TT_MAC_ID_KOREAN, TT_MAC_LANGID_KOREAN, "EUC-KR"}, + {TT_MAC_ID_KOREAN, TT_MAC_LANGID_ANY, "EUC-KR"}, + {TT_MAC_ID_ARABIC, TT_MAC_LANGID_ARABIC, "x-mac-arabic"}, + {TT_MAC_ID_ARABIC, TT_MAC_LANGID_URDU, "x-mac-farsi"}, + {TT_MAC_ID_ARABIC, TT_MAC_LANGID_FARSI, "x-mac-farsi"}, + {TT_MAC_ID_ARABIC, TT_MAC_LANGID_ANY, "x-mac-arabic"}, + {TT_MAC_ID_HEBREW, TT_MAC_LANGID_HEBREW, "x-mac-hebrew"}, + {TT_MAC_ID_HEBREW, TT_MAC_LANGID_ANY, "x-mac-hebrew"}, + {TT_MAC_ID_GREEK, TT_MAC_LANGID_ANY, "x-mac-greek"}, + {TT_MAC_ID_RUSSIAN, TT_MAC_LANGID_ANY, "x-mac-cyrillic"}, + {TT_MAC_ID_DEVANAGARI, TT_MAC_LANGID_ANY, "x-mac-devanagari"}, + {TT_MAC_ID_GURMUKHI, TT_MAC_LANGID_ANY, "x-mac-gurmukhi"}, + {TT_MAC_ID_GUJARATI, TT_MAC_LANGID_ANY, "x-mac-gujarati"}, + { + TT_MAC_ID_TRADITIONAL_CHINESE, + TT_MAC_LANGID_CHINESE_SIMPLIFIED, + "Big5" + }, + { + TT_MAC_ID_TRADITIONAL_CHINESE, + TT_MAC_LANGID_ANY, + "Big5" + }, + { + TT_MAC_ID_SIMPLIFIED_CHINESE, + TT_MAC_LANGID_CHINESE_SIMPLIFIED, + "GB2312" + }, + { + TT_MAC_ID_SIMPLIFIED_CHINESE, + TT_MAC_LANGID_ANY, + "GB2312" + } +}; + +const char *iso_codes[] = { + "us-ascii", + NULL, + "iso-8859-1" +}; + +const char *ms_codes[] = { + "UTF-16BE", + "UTF-16BE", + "Shift_JIS", + NULL, + "Big5", + NULL, + NULL, + NULL, + NULL, + NULL, + "UTF-16BE" +}; + +static const size_t mac_code_count = sizeof(mac_codes) / sizeof(mac_codes[0]); +static const size_t iso_code_count = sizeof(iso_codes) / sizeof(iso_codes[0]); +static const size_t ms_code_count = sizeof(ms_codes) / sizeof(ms_codes[0]); + +static const char *get_mac_code(uint16_t encoding_id, uint16_t language_id) +{ + for (size_t i = 0; i < mac_code_count; i++) { + const struct mac_font_mapping *mac_code = &mac_codes[i]; + + if (mac_code->encoding_id == encoding_id && + mac_code->language_id == language_id) + return mac_code->code_page; + } + + return NULL; +} + +static const char *get_code_page_for_font(uint16_t platform_id, + uint16_t encoding_id, uint16_t language_id) +{ + const char *ret; + + switch (platform_id) { + case TT_PLATFORM_APPLE_UNICODE: + return "UTF-16BE"; + case TT_PLATFORM_MACINTOSH: + ret = get_mac_code(encoding_id, language_id); + if (!ret) + ret = get_mac_code(encoding_id, TT_MAC_LANGID_ANY); + return ret; + case TT_PLATFORM_ISO: + if (encoding_id < iso_code_count) + return iso_codes[encoding_id]; + break; + case TT_PLATFORM_MICROSOFT: + if (encoding_id < ms_code_count) + return ms_codes[encoding_id]; + break; + } + + return NULL; +} + +char *sfnt_name_to_utf8(FT_SfntName *sfnt_name) +{ + const char *charset = get_code_page_for_font(sfnt_name->platform_id, + sfnt_name->encoding_id, sfnt_name->language_id); + char utf8[256]; + char *conv_in, *conv_out; + size_t in_len, out_len; + + iconv_t ic = iconv_open("UTF-8", charset); + if (ic == (iconv_t)-1) { + blog(LOG_WARNING, "couldn't intialize font code page " + "conversion: '%s' to 'utf-8': errno = %d", + charset, (int)errno); + return NULL; + } + + conv_in = (char*)sfnt_name->string; + conv_out = utf8; + in_len = sfnt_name->string_len; + out_len = 256; + + size_t n = iconv(ic, &conv_in, &in_len, &conv_out, &out_len); + if (n == (size_t)-1) { + blog(LOG_WARNING, "couldn't convert font name text: errno = %d", + (int)errno); + iconv_close(ic); + return NULL; + } + + iconv_close(ic); + *conv_out = 0; + return bstrdup(utf8); +} diff --git a/plugins/text-freetype2/find-font-windows.c b/plugins/text-freetype2/find-font-windows.c new file mode 100644 index 000000000..fde8930fb --- /dev/null +++ b/plugins/text-freetype2/find-font-windows.c @@ -0,0 +1,248 @@ +#include +#include +#include "find-font.h" +#include "text-freetype2.h" + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include + +extern DARRAY(struct font_path_info) font_list; + +struct mac_font_mapping { + unsigned short encoding_id; + unsigned short language_id; + unsigned int code_page; +}; + +#define TT_MAC_LANGID_ANY 0xFFFF + +static const struct mac_font_mapping mac_codes[] = { + {TT_MAC_ID_ROMAN, TT_MAC_LANGID_ENGLISH, 10000}, + {TT_MAC_ID_ROMAN, TT_MAC_LANGID_ICELANDIC, 10079}, + {TT_MAC_ID_ROMAN, TT_MAC_LANGID_TURKISH, 10081}, + {TT_MAC_ID_ROMAN, TT_MAC_LANGID_POLISH, 10029}, + {TT_MAC_ID_ROMAN, TT_MAC_LANGID_ROMANIAN, 10010}, + {TT_MAC_ID_ROMAN, TT_MAC_LANGID_CZECH, 10029}, + {TT_MAC_ID_ROMAN, TT_MAC_LANGID_SLOVAK, 10029}, + {TT_MAC_ID_ROMAN, TT_MAC_LANGID_ANY, 10000}, + {TT_MAC_ID_JAPANESE, TT_MAC_LANGID_JAPANESE, 932}, + {TT_MAC_ID_JAPANESE, TT_MAC_LANGID_ANY, 932}, + {TT_MAC_ID_TRADITIONAL_CHINESE,TT_MAC_LANGID_CHINESE_SIMPLIFIED, 950}, + {TT_MAC_ID_TRADITIONAL_CHINESE,TT_MAC_LANGID_ANY, 950}, + {TT_MAC_ID_KOREAN, TT_MAC_LANGID_KOREAN, 51949}, + {TT_MAC_ID_KOREAN, TT_MAC_LANGID_ANY, 51949}, + {TT_MAC_ID_ARABIC, TT_MAC_LANGID_ARABIC, 10004}, + {TT_MAC_ID_ARABIC, TT_MAC_LANGID_URDU, 0}, + {TT_MAC_ID_ARABIC, TT_MAC_LANGID_FARSI, 0}, + {TT_MAC_ID_ARABIC, TT_MAC_LANGID_ANY, 10004}, + {TT_MAC_ID_HEBREW, TT_MAC_LANGID_HEBREW, 10005}, + {TT_MAC_ID_HEBREW, TT_MAC_LANGID_ANY, 10005}, + {TT_MAC_ID_GREEK, TT_MAC_LANGID_ANY, 10006}, + {TT_MAC_ID_RUSSIAN, TT_MAC_LANGID_ANY, 10007}, + {TT_MAC_ID_DEVANAGARI, TT_MAC_LANGID_ANY, 0}, + {TT_MAC_ID_GURMUKHI, TT_MAC_LANGID_ANY, 0}, + {TT_MAC_ID_GUJARATI, TT_MAC_LANGID_ANY, 0}, + {TT_MAC_ID_SIMPLIFIED_CHINESE, TT_MAC_LANGID_CHINESE_SIMPLIFIED, 936}, + {TT_MAC_ID_SIMPLIFIED_CHINESE, TT_MAC_LANGID_ANY, 936} +}; + +unsigned int iso_codes[] = { + 20127, + 0, + 28591 +}; + +unsigned int ms_codes[] = { + 1201, + 1201, + 932, + 0, + 950, + 0, + 0, + 0, + 0, + 0, + 1201 +}; + +static const size_t mac_code_count = sizeof(mac_codes) / sizeof(mac_codes[0]); +static const size_t iso_code_count = sizeof(iso_codes) / sizeof(iso_codes[0]); +static const size_t ms_code_count = sizeof(ms_codes) / sizeof(ms_codes[0]); + +static unsigned int get_mac_code(uint16_t encoding_id, uint16_t language_id) +{ + for (size_t i = 0; i < mac_code_count; i++) { + const struct mac_font_mapping *mac_code = &mac_codes[i]; + + if (mac_code->encoding_id == encoding_id && + mac_code->language_id == language_id) + return mac_code->code_page; + } + + return 0; +} + +static unsigned int get_code_page_for_font(uint16_t platform_id, + uint16_t encoding_id, uint16_t language_id) +{ + unsigned int ret; + + switch (platform_id) { + case TT_PLATFORM_APPLE_UNICODE: + return 1201; + case TT_PLATFORM_MACINTOSH: + ret = get_mac_code(encoding_id, language_id); + if (!ret) + ret = get_mac_code(encoding_id, TT_MAC_LANGID_ANY); + return ret; + case TT_PLATFORM_ISO: + if (encoding_id < iso_code_count) + return iso_codes[encoding_id]; + break; + case TT_PLATFORM_MICROSOFT: + if (encoding_id < ms_code_count) + return ms_codes[encoding_id]; + break; + } + + return 0; +} + +static char *wide_to_utf8(const wchar_t *str, size_t len) +{ + size_t utf8_len; + char *utf8_str = NULL; + + utf8_len = (size_t)WideCharToMultiByte(CP_UTF8, 0, str, (int)len, + NULL, 0, NULL, false); + if (utf8_len) { + utf8_str = bzalloc(utf8_len + 1); + utf8_len = (size_t)WideCharToMultiByte(CP_UTF8, 0, + str, (int)len, + utf8_str, (int)utf8_len + 1, NULL, false); + + if (!utf8_len) { + bfree(utf8_str); + utf8_str = NULL; + } + } + + return utf8_str; +} + +static char *convert_utf16_be_to_utf8(FT_SfntName *sfnt_name) +{ + size_t utf16_len = sfnt_name->string_len / 2; + wchar_t *utf16_str = malloc((utf16_len + 1) * sizeof(wchar_t)); + char *utf8_str = NULL; + + utf16_str[utf16_len] = 0; + + /* convert to little endian */ + for (size_t i = 0; i < utf16_len; i++) { + size_t pos = i * 2; + wchar_t ch = *(wchar_t *)&sfnt_name->string[pos]; + + utf16_str[i] = ((ch >> 8) & 0xFF) | ((ch << 8) & 0xFF00); + } + + utf8_str = wide_to_utf8(utf16_str, utf16_len); + + free(utf16_str); + return utf8_str; +} + +char *sfnt_name_to_utf8(FT_SfntName *sfnt_name) +{ + unsigned int code_page = get_code_page_for_font( + sfnt_name->platform_id, + sfnt_name->encoding_id, + sfnt_name->language_id); + + char *utf8_str = NULL; + wchar_t *utf16_str; + size_t utf16_len; + + if (code_page == 1201) + return convert_utf16_be_to_utf8(sfnt_name); + else if (code_page == 0) + return NULL; + + utf16_len = MultiByteToWideChar(code_page, 0, + sfnt_name->string, sfnt_name->string_len, NULL, 0); + if (utf16_len) { + utf16_str = malloc((utf16_len + 1) * sizeof(wchar_t)); + utf16_len = MultiByteToWideChar(code_page, 0, + sfnt_name->string, sfnt_name->string_len, + utf16_str, (int)utf16_len); + + if (utf16_len) { + utf16_str[utf16_len] = 0; + utf8_str = wide_to_utf8(utf16_str, utf16_len); + } + + free(utf16_str); + } + + return utf8_str; +} + +void load_os_font_list(void) +{ + struct dstr path = {0}; + HANDLE handle; + WIN32_FIND_DATAA wfd; + + dstr_reserve(&path, MAX_PATH); + + HRESULT res = SHGetFolderPathA(NULL, CSIDL_FONTS, NULL, + SHGFP_TYPE_CURRENT, path.array); + if (res != S_OK) { + blog(LOG_WARNING, "Error finding windows font folder"); + return; + } + + path.len = strlen(path.array); + dstr_cat(&path, "\\*.*"); + + handle = FindFirstFileA(path.array, &wfd); + if (handle == INVALID_HANDLE_VALUE) + goto free_string; + + dstr_resize(&path, path.len - 4); + + do { + struct dstr full_path = {0}; + FT_Face face; + FT_Long idx = 0; + FT_Long max_faces = 1; + + if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + continue; + + dstr_copy_dstr(&full_path, &path); + dstr_cat(&full_path, "\\"); + dstr_cat(&full_path, wfd.cFileName); + + while (idx < max_faces) { + FT_Error ret = FT_New_Face(ft2_lib, full_path.array, + idx, &face); + if (ret != 0) + break; + + build_font_path_info(face, idx++, full_path.array); + max_faces = face->num_faces; + FT_Done_Face(face); + } + + dstr_free(&full_path); + } while (FindNextFileA(handle, &wfd)); + + FindClose(handle); + +free_string: + dstr_free(&path); +} diff --git a/plugins/text-freetype2/find-font.c b/plugins/text-freetype2/find-font.c new file mode 100644 index 000000000..c1a3dfa8a --- /dev/null +++ b/plugins/text-freetype2/find-font.c @@ -0,0 +1,202 @@ +#include +#include +#include "find-font.h" + +DARRAY(struct font_path_info) font_list; + +static void create_bitmap_sizes(struct font_path_info *info, FT_Face face) +{ + DARRAY(int) sizes; + + if (!info->is_bitmap) { + info->num_sizes = 0; + info->sizes = NULL; + return; + } + + da_init(sizes); + da_reserve(sizes, face->num_fixed_sizes); + + for (int i = 0; i < face->num_fixed_sizes; i++) { + int val = face->available_sizes[i].size >> 6; + da_push_back(sizes, &val); + } + + info->sizes = sizes.array; + info->num_sizes = face->num_fixed_sizes; +} + +static void add_font_path(FT_Face face, + FT_Long idx, + const char *family_in, + const char *style_in, + const char *path) +{ + struct dstr face_and_style = {0}; + struct font_path_info info; + + dstr_copy(&face_and_style, family_in); + if (face->style_name) { + struct dstr style = {0}; + + dstr_copy(&style, style_in); + dstr_replace(&style, "Bold", ""); + dstr_replace(&style, "Italic", ""); + dstr_replace(&style, " ", " "); + dstr_depad(&style); + + if (!dstr_is_empty(&style)) { + dstr_cat(&face_and_style, " "); + dstr_cat_dstr(&face_and_style, &style); + } + + dstr_free(&style); + } + + info.face_and_style = face_and_style.array; + info.full_len = face_and_style.len; + info.face_len = strlen(family_in); + + info.is_bitmap = !!(face->face_flags & FT_FACE_FLAG_FIXED_SIZES); + info.bold = !!(face->style_flags & FT_STYLE_FLAG_BOLD); + info.italic = !!(face->style_flags & FT_STYLE_FLAG_ITALIC); + info.index = idx; + + info.path = bstrdup(path); + + create_bitmap_sizes(&info, face); + da_push_back(font_list, &info); + + /*blog(LOG_DEBUG, "name: %s\n\tstyle: %s\n\tpath: %s\n", + family_in, + style_in, + path);*/ +} + +void build_font_path_info(FT_Face face, FT_Long idx, const char *path) +{ + FT_UInt num_names = FT_Get_Sfnt_Name_Count(face); + DARRAY(char*) family_names; + + da_init(family_names); + da_push_back(family_names, &face->family_name); + + for (FT_UInt i = 0; i < num_names; i++) { + FT_SfntName name; + char *family; + FT_Error ret = FT_Get_Sfnt_Name(face, i, &name); + + if (ret != 0 || name.name_id != TT_NAME_ID_FONT_FAMILY) + continue; + + family = sfnt_name_to_utf8(&name); + if (!family) + continue; + + for (size_t i = 0; i < family_names.num; i++) { + if (astrcmpi(family_names.array[i], family) == 0) { + bfree(family); + family = NULL; + break; + } + } + + if (family) + da_push_back(family_names, &family); + } + + for (size_t i = 0; i < family_names.num; i++) { + add_font_path(face, idx, family_names.array[i], + face->style_name, path); + + /* first item isn't our allocation */ + if (i > 0) + bfree(family_names.array[i]); + } + + da_free(family_names); +} + +void free_os_font_list(void) +{ + for (size_t i = 0; i < font_list.num; i++) + font_path_info_free(font_list.array + i); + da_free(font_list); +} + +static inline size_t get_rating(struct font_path_info *info, struct dstr *cmp) +{ + const char *src = info->face_and_style; + const char *dst = cmp->array; + size_t num = 0; + + do { + char ch1 = (char)toupper(*src); + char ch2 = (char)toupper(*dst); + + if (ch1 != ch2) + break; + + num++; + } while (*src++ && *dst++); + + return num; +} + +const char *get_font_path(const char *family, uint16_t size, const char *style, + uint32_t flags, FT_Long *idx) +{ + const char *best_path = NULL; + double best_rating = 0.0; + struct dstr face_and_style = {0}; + struct dstr style_str = {0}; + bool bold = !!(flags & OBS_FONT_BOLD); + bool italic = !!(flags & OBS_FONT_ITALIC); + + if (!family) + return NULL; + + dstr_copy(&style_str, style); + dstr_replace(&style_str, "Bold", ""); + dstr_replace(&style_str, "Italic", ""); + dstr_replace(&style_str, " ", " "); + dstr_depad(&style_str); + + dstr_copy(&face_and_style, family); + if (!dstr_is_empty(&style_str)) { + dstr_cat(&face_and_style, " "); + dstr_cat_dstr(&face_and_style, &style_str); + } + + for (size_t i = 0; i < font_list.num; i++) { + struct font_path_info *info = font_list.array + i; + + double rating = (double)get_rating(info, &face_and_style); + if (rating < info->face_len) + continue; + + if (info->is_bitmap) { + int best_diff = 1000; + for (size_t j = 0; j < info->num_sizes; j++) { + int diff = abs(info->sizes[j] - size); + if (diff < best_diff) + best_diff = diff; + } + + rating /= (double)(best_diff + 1.0); + } + + if (info->bold == bold) rating += 1.0; + if (info->italic == italic) rating += 1.0; + + if (rating > best_rating) { + best_path = info->path; + *idx = info->index; + best_rating = rating; + } + } + + dstr_free(&style_str); + dstr_free(&face_and_style); + return best_path; +} diff --git a/plugins/text-freetype2/find-font.h b/plugins/text-freetype2/find-font.h new file mode 100644 index 000000000..1543175c2 --- /dev/null +++ b/plugins/text-freetype2/find-font.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include FT_FREETYPE_H +#include FT_SFNT_NAMES_H +#include FT_TRUETYPE_IDS_H + +#include +#include + +struct font_path_info { + char *face_and_style; + size_t full_len; + size_t face_len; + + bool is_bitmap; + size_t num_sizes; + int *sizes; + + bool bold; + bool italic; + + char *path; + FT_Long index; +}; + +static inline void font_path_info_free(struct font_path_info *info) +{ + bfree(info->sizes); + bfree(info->face_and_style); + bfree(info->path); +} + +extern void build_font_path_info(FT_Face face, FT_Long idx, const char *path); +extern char *sfnt_name_to_utf8(FT_SfntName *sfnt_name); + +extern void load_os_font_list(void); +extern void free_os_font_list(void); +extern const char *get_font_path(const char *family, uint16_t size, + const char *style, uint32_t flags, FT_Long *idx); diff --git a/plugins/text-freetype2/text-freetype2.c b/plugins/text-freetype2/text-freetype2.c index 66deba405..c78ab0629 100644 --- a/plugins/text-freetype2/text-freetype2.c +++ b/plugins/text-freetype2/text-freetype2.c @@ -22,6 +22,7 @@ along with this program. If not, see . #include #include "text-freetype2.h" #include "obs-convenience.h" +#include "find-font.h" FT_Library ft2_lib; @@ -54,6 +55,8 @@ bool obs_module_load() return false; } + load_os_font_list(); + obs_register_source(&freetype2_source_info); return true; @@ -61,6 +64,7 @@ bool obs_module_load() void obs_module_unload(void) { + free_os_font_list(); FT_Done_FreeType(ft2_lib); } @@ -89,15 +93,13 @@ static obs_properties_t ft2_source_properties(void) //obs_property_t prop; // TODO: - // Get system fonts, no idea how. // Scrolling. Can't think of a way to do it with the render // targets currently being broken. (0.4.2) // Better/pixel shader outline/drop shadow // Some way to pull text files from network, I dunno - obs_properties_add_path(props, - "font_file", obs_module_text("FontFile"), - OBS_PATH_FILE, "All font formats (*.ttf *.otf);;", NULL); + obs_properties_add_font(props, "font", + obs_module_text("Font")); obs_properties_add_text(props, "text", obs_module_text("Text"), OBS_TEXT_MULTILINE); @@ -112,9 +114,6 @@ static obs_properties_t ft2_source_properties(void) "text_file", obs_module_text("TextFile"), OBS_PATH_FILE, "All font formats (*.txt);;", NULL); - obs_properties_add_int(props, "font_size", - obs_module_text("FontSize"), 0, 72, 1); - obs_properties_add_color(props, "color1", obs_module_text("Color1")); @@ -154,6 +153,8 @@ static void ft2_source_destroy(void *data) if (srcdata->font_name != NULL) bfree(srcdata->font_name); + if (srcdata->font_style != NULL) + bfree(srcdata->font_style); if (srcdata->text != NULL) bfree(srcdata->text); if (srcdata->texbuf != NULL) @@ -207,9 +208,10 @@ static void ft2_video_tick(void *data, float seconds) if (srcdata->text_file == NULL) return; if (os_gettime_ns() - srcdata->last_checked >= 1000000000) { + time_t t = get_modified_timestamp(srcdata->text_file); srcdata->last_checked = os_gettime_ns(); - if (srcdata->m_timestamp != - get_modified_timestamp(srcdata->text_file)) { + + if (srcdata->m_timestamp != t) { if (srcdata->log_mode) read_from_end(srcdata, srcdata->text_file); else @@ -222,15 +224,39 @@ static void ft2_video_tick(void *data, float seconds) UNUSED_PARAMETER(seconds); } +static bool init_font(struct ft2_source *srcdata) +{ + FT_Long index; + const char *path = get_font_path(srcdata->font_name, srcdata->font_size, + srcdata->font_style, srcdata->font_flags, &index); + if (!path) + return false; + + if (srcdata->font_face != NULL) { + FT_Done_Face(srcdata->font_face); + srcdata->font_face = NULL; + } + + return FT_New_Face(ft2_lib, path, index, &srcdata->font_face) == 0; +} + static void ft2_source_update(void *data, obs_data_t settings) { struct ft2_source *srcdata = data; - const char *tmp = obs_data_get_string(settings, "font_file"); + obs_data_t font_obj = obs_data_get_obj(settings, "font"); bool vbuf_needs_update = false; bool word_wrap = false; uint32_t color[2]; uint32_t custom_width = 0; + const char *font_name = obs_data_get_string(font_obj, "face"); + const char *font_style = obs_data_get_string(font_obj, "style"); + uint16_t font_size = (uint16_t)obs_data_get_int(font_obj, "size"); + uint32_t font_flags = (uint32_t)obs_data_get_int(font_obj, "flags"); + + if (!font_obj) + return; + srcdata->drop_shadow = obs_data_get_bool(settings, "drop_shadow"); srcdata->outline_text = obs_data_get_bool(settings, "outline"); word_wrap = obs_data_get_bool(settings, "word_wrap"); @@ -295,38 +321,34 @@ static void ft2_source_update(void *data, obs_data_t settings) } } - if (srcdata->font_size != obs_data_get_int(settings, "font_size")) + if (srcdata->font_size != font_size) vbuf_needs_update = true; if (srcdata->font_name != NULL) { - if (strcmp(tmp, srcdata->font_name) == 0 && - srcdata->font_size == - obs_data_get_int(settings, "font_size")) - goto skip_font_load; + if (strcmp(font_name, srcdata->font_name) == 0 && + strcmp(font_style, srcdata->font_style) == 0 && + font_flags == srcdata->font_flags && + font_size == srcdata->font_size) + goto skip_font_load; + bfree(srcdata->font_name); + bfree(srcdata->font_style); srcdata->font_name = NULL; + srcdata->font_style = NULL; srcdata->max_h = 0; } - srcdata->font_name = bzalloc(strlen(tmp) + 1); - strcpy(srcdata->font_name, tmp); + srcdata->font_name = bstrdup(font_name); + srcdata->font_style = bstrdup(font_style); + srcdata->font_size = font_size; + srcdata->font_flags = font_flags; - if (srcdata->font_face != NULL) { - FT_Done_Face(srcdata->font_face); - srcdata->font_face = NULL; - } - - FT_New_Face(ft2_lib, srcdata->font_name, 0, - &srcdata->font_face); - - if (srcdata->font_face == NULL) { + if (!init_font(srcdata) || srcdata->font_face == NULL) { blog(LOG_WARNING, "FT2-text: Failed to load font %s", srcdata->font_name); - return; + goto error; } else { - srcdata->font_size = (uint16_t)obs_data_get_int(settings, - "font_size"); FT_Set_Pixel_Sizes(srcdata->font_face, 0, srcdata->font_size); FT_Select_Charmap(srcdata->font_face, FT_ENCODING_UNICODE); } @@ -340,21 +362,20 @@ static void ft2_source_update(void *data, obs_data_t settings) cache_standard_glyphs(srcdata); skip_font_load:; if (from_file) { - tmp = obs_data_get_string(settings, "text_file"); + const char *tmp = obs_data_get_string(settings, "text_file"); if (strlen(tmp) == 0) return; if (srcdata->text_file != NULL) { if (strcmp(srcdata->text_file, tmp) == 0 && !vbuf_needs_update) - return; + goto error; bfree(srcdata->text_file); srcdata->text_file = NULL; } else blog(LOG_WARNING, "FT2-text: Failed to open %s for reading", tmp); - srcdata->text_file = bzalloc(strlen(tmp) + 1); - strcpy(srcdata->text_file, tmp); + srcdata->text_file = bstrdup(tmp); if (chat_log_mode) read_from_end(srcdata, tmp); @@ -363,31 +384,37 @@ skip_font_load:; srcdata->last_checked = os_gettime_ns(); } else { - tmp = obs_data_get_string(settings, "text"); - if (strlen(tmp) == 0) return; + const char *tmp = obs_data_get_string(settings, "text"); + if (strlen(tmp) == 0) goto error; if (srcdata->text != NULL) { bfree(srcdata->text); srcdata->text = NULL; } - srcdata->text = bzalloc((strlen(tmp) + 1)*sizeof(wchar_t)); - os_utf8_to_wcs(tmp, strlen(tmp), srcdata->text, - (strlen(tmp) + 1)); + + os_utf8_to_wcs_ptr(tmp, strlen(tmp), &srcdata->text); } cache_glyphs(srcdata, srcdata->text); set_up_vertex_buffer(srcdata); + +error: + obs_data_release(font_obj); } static void *ft2_source_create(obs_data_t settings, obs_source_t source) { struct ft2_source *srcdata = bzalloc(sizeof(struct ft2_source)); + obs_data_t font_obj = obs_data_create(); srcdata->src = source; srcdata->font_size = 32; - obs_data_set_default_int(settings, "font_size", 32); + obs_data_set_default_string(font_obj, "face", "Arial"); + obs_data_set_default_int(font_obj, "size", 32); + obs_data_set_default_obj(settings, "font", font_obj); + obs_data_set_default_int(settings, "color1", 0xFFFFFFFF); obs_data_set_default_int(settings, "color2", 0xFFFFFFFF); obs_data_set_default_string(settings, "text", @@ -395,5 +422,7 @@ static void *ft2_source_create(obs_data_t settings, obs_source_t source) ft2_source_update(srcdata, settings); + obs_data_release(font_obj); + return srcdata; } diff --git a/plugins/text-freetype2/text-freetype2.h b/plugins/text-freetype2/text-freetype2.h index e7ce846ea..9db0dfa44 100644 --- a/plugins/text-freetype2/text-freetype2.h +++ b/plugins/text-freetype2/text-freetype2.h @@ -28,11 +28,13 @@ struct glyph_info { }; struct ft2_source { - uint8_t font_type; - char *font_name; + char *font_name; + char *font_style; + uint16_t font_size; + uint32_t font_flags; + char *text_file; wchar_t *text; - uint16_t font_size; time_t m_timestamp; uint64_t last_checked;