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;