From 46a6213b186acb9db6a6d7d5dfc059ebc604c987 Mon Sep 17 00:00:00 2001 From: Green Sky Date: Fri, 11 Jul 2025 12:42:00 +0200 Subject: [PATCH 1/9] add font finding for fontconfig and raw filesystem no iteration interface yet --- src/CMakeLists.txt | 4 + src/font_loading/CMakeLists.txt | 45 +++++ src/font_loading/font_finder_fc_sdl.cpp | 218 ++++++++++++++++++++++++ src/font_loading/font_finder_fc_sdl.hpp | 38 +++++ src/font_loading/font_finder_fs.cpp | 114 +++++++++++++ src/font_loading/font_finder_fs.hpp | 30 ++++ src/font_loading/font_finder_i.hpp | 18 ++ src/font_loading/test_ff.cpp | 51 ++++++ 8 files changed, 518 insertions(+) create mode 100644 src/font_loading/CMakeLists.txt create mode 100644 src/font_loading/font_finder_fc_sdl.cpp create mode 100644 src/font_loading/font_finder_fc_sdl.hpp create mode 100644 src/font_loading/font_finder_fs.cpp create mode 100644 src/font_loading/font_finder_fs.hpp create mode 100644 src/font_loading/font_finder_i.hpp create mode 100644 src/font_loading/test_ff.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 41cea53d..c3a332e3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,5 +1,7 @@ cmake_minimum_required(VERSION 3.9...3.24 FATAL_ERROR) +add_subdirectory(./font_loading) + ######################################## if (TOMATO_MAIN_SO) @@ -199,6 +201,8 @@ target_link_libraries(tomato PUBLIC solanaceae_object_store + font_loading + SDL3::SDL3 imgui diff --git a/src/font_loading/CMakeLists.txt b/src/font_loading/CMakeLists.txt new file mode 100644 index 00000000..0f42871d --- /dev/null +++ b/src/font_loading/CMakeLists.txt @@ -0,0 +1,45 @@ +cmake_minimum_required(VERSION 3.9...3.24 FATAL_ERROR) + +######################################## + +add_library(font_loading STATIC) + +target_sources(font_loading PUBLIC + ./font_finder_i.hpp + ./font_finder_fs.hpp + ./font_finder_fs.cpp + ./font_finder_fc_sdl.hpp + ./font_finder_fc_sdl.cpp +) + +# TODO: conditionally compile fontconfig +#if (TOMATO_BREAKPAD) +# target_sources(tomato PUBLIC +# ./breakpad_client.hpp +# ./breakpad_client.cpp +# ) + +# target_link_libraries(tomato PUBLIC breakpad_client) +# target_compile_definitions(tomato PUBLIC TOMATO_BREAKPAD) +#endif() + +target_compile_features(font_loading PUBLIC cxx_std_17) +target_link_libraries(font_loading PUBLIC + SDL3::SDL3 + imgui +) + +set_target_properties(font_loading PROPERTIES POSITION_INDEPENDENT_CODE ON) + +######################################## + +add_executable(test_font_loaders EXCLUDE_FROM_ALL + ./test_ff.cpp +) + +target_link_libraries(test_font_loaders + font_loading +) + +add_test(NAME test_font_loaders COMMAND test_font_loaders) + diff --git a/src/font_loading/font_finder_fc_sdl.cpp b/src/font_loading/font_finder_fc_sdl.cpp new file mode 100644 index 00000000..43d79659 --- /dev/null +++ b/src/font_loading/font_finder_fc_sdl.cpp @@ -0,0 +1,218 @@ +#include "./font_finder_fc_sdl.hpp" + +#include + +#include +#include +#include +#include + +#include + +bool FontFinder_FontConfigSDL::fillCache(void) { + const char *args[] = {"fc-list", "-f", "%{family}:%{style}:%{lang}:%{color}\n", NULL}; + auto* proc = SDL_CreateProcess(args, true); + if (proc == nullptr) { + std::cerr << "failed to create process\n"; + return false; + } + + size_t data_size{0}; + int exit_code{0}; + char* data = static_cast(SDL_ReadProcess(proc, &data_size, &exit_code)); + if (data == nullptr) { + std::cerr << "process returned no data\n"; + return false; // error + } + if (exit_code != 0) { + SDL_free(data); + std::cerr << "process exit code " << exit_code << "\n"; + return false; + } + + std::string_view returned_string{data, data_size}; + + //std::cout << "fc-list:\n" << returned_string << "\n"; + + static constexpr auto for_each_split = [](std::string_view input, const char sep, auto&& fn) { + for (auto next = input.find_first_of(sep); next != std::string_view::npos; next = input.find_first_of(sep)) { + auto segment = input.substr(0, next); + input = input.substr(next+1); + fn(segment); + } + if (!input.empty()) { + fn(input); + } + }; + + // cached outside + std::vector segment_splits; + // split by newline + for_each_split(returned_string, '\n', [&segment_splits, this](auto line) { + if (line.size() == 0) { + return; + } + + segment_splits.clear(); // preserves cap + + // then split by colon (TODO:back to front) + for_each_split(line, ':', [&segment_splits](auto segment) { + segment_splits.push_back(segment); + }); + + //"%{family}:%{style}:%{lang}:%{color}\n" + if (segment_splits.size() < 4) { + std::cerr << "invalid font line! (s:" << segment_splits.size() << ")\n"; + return; + } + + if (segment_splits[0].empty()) { + // empty family, warn? + return; + } + + std::string processed_family{segment_splits[0]}; + processed_family.erase(std::remove(processed_family.begin(), processed_family.end(), ' '), processed_family.end()); + std::transform(processed_family.begin(), processed_family.end(), processed_family.begin(), [](unsigned char c) { return std::tolower(c); }); + _cache.push_back(SystemFont{ + std::string{segment_splits[0]}, + processed_family, + segment_splits[3] == "True" || segment_splits[3] == "true" || segment_splits[3] == "1", + }); +#if 0 + segment_splits[0]; // family + segment_splits[1]; // style + segment_splits[2]; // lang + // langs are concatinated with | + segment_splits[3]; // color +#endif + }); + + SDL_free(data); + return true; +} + +std::string FontFinder_FontConfigSDL::getFontFile(const std::string& font_name) { + const char *args[] = {"fc-match", "-f", "%{file}", font_name.c_str(), NULL}; + + auto* proc = SDL_CreateProcess(args, true); + if (proc == nullptr) { + std::cerr << "failed to create process\n"; + return ""; + } + + size_t data_size{0}; + int exit_code{0}; + char* data = static_cast(SDL_ReadProcess(proc, &data_size, &exit_code)); + if (data == nullptr) { + std::cerr << "process returned no data\n"; + return ""; + } + if (exit_code != 0) { + SDL_free(data); + std::cerr << "process exit code " << exit_code << "\n"; + return ""; + } + + std::string_view returned_string{data, data_size}; + + //std::cout << "fc-match -f file:\n" << returned_string << "\n"; + + std::string return_string{returned_string}; + + SDL_free(data); + + return return_string; +} + +FontFinder_FontConfigSDL::FontFinder_FontConfigSDL(void) { + fillCache(); +} + +void FontFinder_FontConfigSDL::reset(void) { + _cache.clear(); + fillCache(); +} + +// TODO: use fc-match instead, maybe fallback to this +std::string FontFinder_FontConfigSDL::findBest(std::string_view family, std::string_view lang, bool color) const { + const SystemFont* best_ptr = nullptr; + int best_score = 0; + + if (_cache.empty()) { + std::cerr << "Empty system cache\n"; + } + + for (const auto& it : _cache) { + int score = 0; + if (it.family == family) { + score += 10; + } else { + std::string processed_family = std::string{family}; + processed_family.erase(std::remove(processed_family.begin(), processed_family.end(), ' '), processed_family.end()); + std::transform(processed_family.begin(), processed_family.end(), processed_family.begin(), [](unsigned char c) { return std::tolower(c); }); + + if (it.processed_family == processed_family) { + score += 9; + } else { + // search for family as substring in it.family (processed) + if (it.processed_family.find(processed_family) != std::string::npos) { + score += 2; + } + } + } + // TODO: case/fuzzy compare/edit distance + + // TODO: lang! + // TODO: style? or make style part of the name? + + if (it.color != color) { + score -= 1; + } + + if (best_score < score) { + best_score = score; + best_ptr = ⁢ + } + //std::cout << it.family << " had score " << score << "\n"; + } + + if (best_ptr == nullptr) { + return ""; + } else { + return getFontFile(best_ptr->family); + } +} + +// TODO: remove +void fc_sdl_get_font_emoji(void) { + const char *args[] = {"fc-match", "-s", "-f", "%{file}\n%{color}\n", "emoji", NULL}; + // results in eg: + // /nix/store/i8r5whmw46b2p3a4n5ls878jjnlpz4mq-noto-fonts-color-emoji-2.047/share/fonts/noto/NotoColorEmoji.ttf/True + // /usr/share/fonts/noto/NotoEmoji.ttf/False + // ... + + auto* proc = SDL_CreateProcess(args, true); + if (proc == nullptr) { + std::cerr << "failed to create process\n"; + return; // error + } + + size_t data_size{0}; + int exit_code{0}; + char* data = static_cast(SDL_ReadProcess(proc, &data_size, &exit_code)); + if (data == nullptr) { + std::cerr << "process returned no data\n"; + return; // error + } + if (exit_code != 0) { + std::cerr << "process exit code " << exit_code << "\n"; + return; // error + } + + std::string_view returned_string{data, data_size}; + + std::cout << "fc-match emoji:\n" << returned_string << "\n"; + + SDL_free(data); +} diff --git a/src/font_loading/font_finder_fc_sdl.hpp b/src/font_loading/font_finder_fc_sdl.hpp new file mode 100644 index 00000000..4fa46240 --- /dev/null +++ b/src/font_loading/font_finder_fc_sdl.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include "./font_finder_i.hpp" + +#include + +#include +#include +#include +#include + +#include + +// provides a fontconfig (typically linux/unix) implementation +// using commandline tools using sdl process management +struct FontFinder_FontConfigSDL : public FontFinderInterface { + struct SystemFont { + std::string family; + std::string processed_family; + bool color {false}; + + // lang + // for emojies: + // lang: und-zsye + }; + + std::vector _cache; + + bool fillCache(void); + + static std::string getFontFile(const std::string& font_name); + + FontFinder_FontConfigSDL(void); + + void reset(void); + + std::string findBest(std::string_view family, std::string_view lang = "", bool color = false) const override; +}; diff --git a/src/font_loading/font_finder_fs.cpp b/src/font_loading/font_finder_fs.cpp new file mode 100644 index 00000000..408e91c5 --- /dev/null +++ b/src/font_loading/font_finder_fs.cpp @@ -0,0 +1,114 @@ +#include "./font_finder_fs.hpp" + +#include +#include +#include + +#include +#include + +FontFinder_FileSystem::FontFinder_FileSystem(void) { +} + +FontFinder_FileSystem::FontFinder_FileSystem(std::string_view folder_path) { + std::filesystem::path dir{folder_path}; + if (!std::filesystem::is_directory(dir)) { + throw std::runtime_error("path not a directory"); + } + + addFontDir(dir.generic_u8string()); +} + +// more optional params? +void FontFinder_FileSystem::addFontDir(std::string_view path) { + for (const auto& dir_entry : std::filesystem::directory_iterator(path)) { + if (dir_entry.is_directory()) { + addFontDir(dir_entry.path().generic_u8string()); + } else if (dir_entry.is_regular_file()) { + // TODO: test symlinks? + addFontFile(dir_entry.path().generic_u8string()); + } + } +} + +// more optional params? +void FontFinder_FileSystem::addFontFile(std::string_view file_path) { + std::filesystem::path path{file_path}; + std::string filename = path.filename().generic_u8string(); + std::string extension = path.extension().generic_u8string(); + std::transform(extension.begin(), extension.end(), extension.begin(), [](unsigned char c) { return std::tolower(c); }); + + // TODO: more? better ttc? + if (extension != ".ttf" && extension != ".otf" && extension != ".ttc") { + std::cout << "unsupported file extension '" << extension << "'\n"; + return; + } + + if (filename.size() <= extension.size()) { + return; + } + + // remove extension from filename + filename = filename.substr(0, filename.size()-extension.size()); + // filename should now contain UpperCamelCase and an optional -Style + + std::string processed_filename = filename; + processed_filename.erase(std::remove(processed_filename.begin(), processed_filename.end(), ' '), processed_filename.end()); + std::transform(processed_filename.begin(), processed_filename.end(), processed_filename.begin(), [](unsigned char c) { return std::tolower(c); }); + + _cache.push_back({ + filename, + "", // TODO: style + path.generic_u8string(), + processed_filename + }); +} + +std::string FontFinder_FileSystem::findBest(std::string_view family, std::string_view lang, bool color) const { + const SystemFont* best_ptr = nullptr; + int best_score = 0; + + if (_cache.empty()) { + std::cerr << "Empty system cache\n"; + } + + for (const auto& it : _cache) { + int score = 0; + if (it.family == family) { + score += 10; + } else { + std::string processed_family = std::string{family}; + processed_family.erase(std::remove(processed_family.begin(), processed_family.end(), ' '), processed_family.end()); + std::transform(processed_family.begin(), processed_family.end(), processed_family.begin(), [](unsigned char c) { return std::tolower(c); }); + + if (it.processed_family == processed_family) { + score += 9; + } else { + // search for family as substring in it.family (processed) + if (it.processed_family.find(processed_family) != std::string::npos) { + score += 2; + } + } + } + // TODO: fuzzy compare/edit distance + + // TODO: lang! + + //if (it.color != color) { + // score -= 1; + //} + + if (best_score < score) { + best_score = score; + best_ptr = ⁢ + } + //std::cout << it.family << " had score " << score << "\n"; + } + + if (best_ptr == nullptr) { + return ""; + } else { + return best_ptr->file; + } +} + diff --git a/src/font_loading/font_finder_fs.hpp b/src/font_loading/font_finder_fs.hpp new file mode 100644 index 00000000..27fa6dc8 --- /dev/null +++ b/src/font_loading/font_finder_fs.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "./font_finder_i.hpp" + +#include +#include +#include + +// using raw file and folder paths provided and tries to give the best fitting font +// eg on android (/system/fonts/) +struct FontFinder_FileSystem : public FontFinderInterface { + struct SystemFont { + std::string family; + std::string style; + std::string file; + + std::string processed_family; + }; + + std::vector _cache; + + FontFinder_FileSystem(void); + FontFinder_FileSystem(std::string_view folder_path); + + // more optional params? + void addFontDir(std::string_view path); + void addFontFile(std::string_view path); + + std::string findBest(std::string_view family, std::string_view lang = "", bool color = false) const override; +}; diff --git a/src/font_loading/font_finder_i.hpp b/src/font_loading/font_finder_i.hpp new file mode 100644 index 00000000..221ed689 --- /dev/null +++ b/src/font_loading/font_finder_i.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +struct FontFinderInterface { + virtual ~FontFinderInterface(void) {} + + //struct FontIteratorI { + //}; + //virtual FontIteratorI begin(void) const {} + //virtual FontIteratorI end(void) const {} + + // (family is the name of the font) + // "und-zsye" is the preffered emoji font lang + // not every impl supports every field + // returns a font file path, might be empty + virtual std::string findBest(std::string_view family, std::string_view lang = "", bool color = false) const = 0; +}; diff --git a/src/font_loading/test_ff.cpp b/src/font_loading/test_ff.cpp new file mode 100644 index 00000000..ca352ca3 --- /dev/null +++ b/src/font_loading/test_ff.cpp @@ -0,0 +1,51 @@ +#include "./font_finder_i.hpp" +#include "./font_finder_fs.hpp" +#include "./font_finder_fc_sdl.hpp" + +#include + +int main(void) { + std::cout << "Font Finder Tests\n"; + + auto fun_tests = [](FontFinderInterface& ff) { + std::cout << "findBest('Emoji', '', true): '" << ff.findBest("Emoji", "", true) << "'\n"; + + std::cout << "findBest('NotoColorEmoji'): '" << ff.findBest("NotoColorEmoji") << "'\n"; + std::cout << "findBest('Noto Color Emoji'): '" << ff.findBest("Noto Color Emoji") << "'\n"; + std::cout << "findBest('NotoSans'): '" << ff.findBest("NotoSans") << "'\n"; + std::cout << "findBest('Noto Sans'): '" << ff.findBest("Noto Sans") << "'\n"; + + // windows file name + std::cout << "findBest('Seguiemj'): '" << ff.findBest("Seguiemj") << "'\n"; + std::cout << "findBest('Segoe UI Emoji'): '" << ff.findBest("Segoe UI Emoji") << "'\n"; + std::cout << "findBest('Segoe UI'): '" << ff.findBest("Segoe UI") << "'\n"; + }; + + + std::cout << std::string(20, '=') << " FCSDL " << std::string(20, '=') << "\n"; + try { + FontFinder_FontConfigSDL ff; + fun_tests(ff); + } catch (...) { + std::cerr << "caught exception\n"; + } + + std::cout << std::string(20, '=') << " FS " << std::string(20, '=') << "\n"; + try { +#if defined(_WIN32) || defined(WIN32) + FontFinder_FileSystem ff{"C:\\Windows\\Fonts"}; +#elif __ANDROID__ + FontFinder_FileSystem ff{"/system/fonts"}; +//#elif mac +///System Folder/Fonts/ +#else + FontFinder_FileSystem ff{"/usr/share/fonts"}; +#endif + fun_tests(ff); + } catch (...) { + std::cerr << "caught exception\n"; + } + + return 0; +} + From a8bb1168db699e8d4012514b153cb2988b4efcb1 Mon Sep 17 00:00:00 2001 From: Green Sky Date: Fri, 11 Jul 2025 12:49:08 +0200 Subject: [PATCH 2/9] wip: enable fontfinding tests in ci --- .github/workflows/ci.yml | 14 +++++++++++--- CMakeLists.txt | 4 ++++ src/font_loading/CMakeLists.txt | 16 +++++++++------- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7ef21d3f..b50ebf6e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,10 +37,14 @@ jobs: key: ${{github.event.repository.name}}-${{github.job}}-${{matrix.os}}-${{matrix.type}} - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DTOMATO_ASAN=${{matrix.type == 'asan' && 'ON' || 'OFF'}} + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DTOMATO_ASAN=${{matrix.type == 'asan' && 'ON' || 'OFF'}} -DBUILD_TESTING=ON - name: Build - run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -j 4 -t tomato + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -j 4 -t tomato test_font_loaders + + - name: Test + working-directory: ${{github.workspace}}/build + run: ctest -V -C ${{env.BUILD_TYPE}} android: timeout-minutes: 30 @@ -190,5 +194,9 @@ jobs: -DPKG_CONFIG_EXECUTABLE=C:/vcpkg/installed/x64-windows/tools/pkgconf/pkgconf.exe - name: Build - run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -j 4 -t tomato + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -j 4 -t tomato test_font_loaders + + - name: Test + working-directory: ${{github.workspace}}/build + run: ctest -V -C ${{env.BUILD_TYPE}} diff --git a/CMakeLists.txt b/CMakeLists.txt index 14ab7d8a..26b98b9e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -134,6 +134,10 @@ message(STATUS "${tomato_VERSION_MAJOR}.${tomato_VERSION_MINOR}.${tomato_VERSION # cmake setup end +if (BUILD_TESTING) + include(CTest) +endif() + add_subdirectory(./src) # TODO: move to src diff --git a/src/font_loading/CMakeLists.txt b/src/font_loading/CMakeLists.txt index 0f42871d..465c1659 100644 --- a/src/font_loading/CMakeLists.txt +++ b/src/font_loading/CMakeLists.txt @@ -33,13 +33,15 @@ set_target_properties(font_loading PROPERTIES POSITION_INDEPENDENT_CODE ON) ######################################## -add_executable(test_font_loaders EXCLUDE_FROM_ALL - ./test_ff.cpp -) +if (BUILD_TESTING) + add_executable(test_font_loaders + ./test_ff.cpp + ) -target_link_libraries(test_font_loaders - font_loading -) + target_link_libraries(test_font_loaders + font_loading + ) -add_test(NAME test_font_loaders COMMAND test_font_loaders) + add_test(NAME test_font_loaders COMMAND test_font_loaders) +endif() From aebf7a31f99badb497663053b55d76627faeab3a Mon Sep 17 00:00:00 2001 From: Green Sky Date: Sun, 13 Jul 2025 14:02:15 +0200 Subject: [PATCH 3/9] mac testing --- .github/workflows/ci.yml | 6 +++++- src/font_loading/font_finder_fc_sdl.cpp | 7 ++++--- src/font_loading/test_ff.cpp | 15 +++++++++++++-- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b50ebf6e..e51b64f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -148,7 +148,11 @@ jobs: run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - name: Build - run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -j 4 -t tomato + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -j 4 -t tomato test_font_loaders + + - name: Test + working-directory: ${{github.workspace}}/build + run: ctest -V -C ${{env.BUILD_TYPE}} windows: timeout-minutes: 15 diff --git a/src/font_loading/font_finder_fc_sdl.cpp b/src/font_loading/font_finder_fc_sdl.cpp index 43d79659..51ebc463 100644 --- a/src/font_loading/font_finder_fc_sdl.cpp +++ b/src/font_loading/font_finder_fc_sdl.cpp @@ -2,6 +2,7 @@ #include +#include #include #include #include @@ -13,7 +14,7 @@ bool FontFinder_FontConfigSDL::fillCache(void) { const char *args[] = {"fc-list", "-f", "%{family}:%{style}:%{lang}:%{color}\n", NULL}; auto* proc = SDL_CreateProcess(args, true); if (proc == nullptr) { - std::cerr << "failed to create process\n"; + throw std::runtime_error("failed to create process"); return false; } @@ -21,12 +22,12 @@ bool FontFinder_FontConfigSDL::fillCache(void) { int exit_code{0}; char* data = static_cast(SDL_ReadProcess(proc, &data_size, &exit_code)); if (data == nullptr) { - std::cerr << "process returned no data\n"; + throw std::runtime_error("process returned no data"); return false; // error } if (exit_code != 0) { SDL_free(data); - std::cerr << "process exit code " << exit_code << "\n"; + throw std::runtime_error("process exit code " + std::to_string(exit_code)); return false; } diff --git a/src/font_loading/test_ff.cpp b/src/font_loading/test_ff.cpp index ca352ca3..0d26d71e 100644 --- a/src/font_loading/test_ff.cpp +++ b/src/font_loading/test_ff.cpp @@ -3,12 +3,15 @@ #include "./font_finder_fc_sdl.hpp" #include +#include int main(void) { std::cout << "Font Finder Tests\n"; auto fun_tests = [](FontFinderInterface& ff) { + std::cout << "findBest('ColorEmoji', '', true): '" << ff.findBest("ColorEmoji", "", true) << "'\n"; std::cout << "findBest('Emoji', '', true): '" << ff.findBest("Emoji", "", true) << "'\n"; + std::cout << "findBest('Emoji', '', false): '" << ff.findBest("Emoji", "", false) << "'\n"; std::cout << "findBest('NotoColorEmoji'): '" << ff.findBest("NotoColorEmoji") << "'\n"; std::cout << "findBest('Noto Color Emoji'): '" << ff.findBest("Noto Color Emoji") << "'\n"; @@ -19,6 +22,11 @@ int main(void) { std::cout << "findBest('Seguiemj'): '" << ff.findBest("Seguiemj") << "'\n"; std::cout << "findBest('Segoe UI Emoji'): '" << ff.findBest("Segoe UI Emoji") << "'\n"; std::cout << "findBest('Segoe UI'): '" << ff.findBest("Segoe UI") << "'\n"; + + // macos + std::cout << "findBest('Apple Color Emoji'): '" << ff.findBest("Apple Color Emoji") << "'\n"; + std::cout << "findBest('Helvetica'): '" << ff.findBest("Helvetica") << "'\n"; + std::cout << "findBest('San Francisco'): '" << ff.findBest("San Francisco") << "'\n"; }; @@ -36,12 +44,15 @@ int main(void) { FontFinder_FileSystem ff{"C:\\Windows\\Fonts"}; #elif __ANDROID__ FontFinder_FileSystem ff{"/system/fonts"}; -//#elif mac -///System Folder/Fonts/ +#elif __APPLE__ + // TODO: macos only + FontFinder_FileSystem ff{"/System/Library/Fonts"}; #else FontFinder_FileSystem ff{"/usr/share/fonts"}; #endif fun_tests(ff); + } catch (const std::runtime_error& e) { + std::cerr << "caught runtime_error exception '" << e.what() << "'\n"; } catch (...) { std::cerr << "caught exception\n"; } From 4160d098be2f6b4a94f6ad97cbd0cfca2bde3328 Mon Sep 17 00:00:00 2001 From: Green Sky Date: Sat, 11 Oct 2025 20:29:00 +0200 Subject: [PATCH 4/9] add system default aliases for linux to tests --- src/font_loading/test_ff.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/font_loading/test_ff.cpp b/src/font_loading/test_ff.cpp index 0d26d71e..2966b12a 100644 --- a/src/font_loading/test_ff.cpp +++ b/src/font_loading/test_ff.cpp @@ -27,6 +27,15 @@ int main(void) { std::cout << "findBest('Apple Color Emoji'): '" << ff.findBest("Apple Color Emoji") << "'\n"; std::cout << "findBest('Helvetica'): '" << ff.findBest("Helvetica") << "'\n"; std::cout << "findBest('San Francisco'): '" << ff.findBest("San Francisco") << "'\n"; + + // system default aliases (typical on linux with fc) + std::cout << "findBest('serif'): '" << ff.findBest("serif") << "'\n"; + std::cout << "findBest('serif', 'ar'): '" << ff.findBest("serif", "ar") << "'\n"; + std::cout << "findBest('serif', 'ja'): '" << ff.findBest("serif", "ja") << "'\n"; + std::cout << "findBest('sans-serif'): '" << ff.findBest("sans-serif") << "'\n"; + std::cout << "findBest('sans-serif', 'ar'): '" << ff.findBest("sans-serif", "ar") << "'\n"; + std::cout << "findBest('sans-serif', 'ja'): '" << ff.findBest("sans-serif", "ja") << "'\n"; + std::cout << "findBest('monospace'): '" << ff.findBest("monospace") << "'\n"; }; From 46c1081a8eaec15296afff89c9d5c0d73ef13abe Mon Sep 17 00:00:00 2001 From: Green Sky Date: Sat, 11 Oct 2025 21:03:03 +0200 Subject: [PATCH 5/9] add lang to own fc matcher and fix scoring --- src/font_loading/font_finder_fc_sdl.cpp | 29 ++++++++++++++++++++----- src/font_loading/font_finder_fc_sdl.hpp | 10 ++++----- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/font_loading/font_finder_fc_sdl.cpp b/src/font_loading/font_finder_fc_sdl.cpp index 51ebc463..f66503b9 100644 --- a/src/font_loading/font_finder_fc_sdl.cpp +++ b/src/font_loading/font_finder_fc_sdl.cpp @@ -75,9 +75,14 @@ bool FontFinder_FontConfigSDL::fillCache(void) { std::string processed_family{segment_splits[0]}; processed_family.erase(std::remove(processed_family.begin(), processed_family.end(), ' '), processed_family.end()); std::transform(processed_family.begin(), processed_family.end(), processed_family.begin(), [](unsigned char c) { return std::tolower(c); }); + + std::string processed_lang{segment_splits[2]}; + std::transform(processed_lang.begin(), processed_lang.end(), processed_lang.begin(), [](unsigned char c) { return std::tolower(c); }); + _cache.push_back(SystemFont{ std::string{segment_splits[0]}, processed_family, + processed_lang, segment_splits[3] == "True" || segment_splits[3] == "true" || segment_splits[3] == "1", }); #if 0 @@ -164,18 +169,32 @@ std::string FontFinder_FontConfigSDL::findBest(std::string_view family, std::str } // TODO: case/fuzzy compare/edit distance - // TODO: lang! - // TODO: style? or make style part of the name? + if (score > 0) { // extra selectors after the family + if (lang.empty()) { + if (!it.lang.empty()) { + score -= 1; + } + } else { + if (it.lang.find(lang) != std::string::npos) { + score += 5; + } + } + + // TODO: style? or make style part of the name? - if (it.color != color) { - score -= 1; + if (it.color != color) { + score -= 1; + } } if (best_score < score) { best_score = score; best_ptr = ⁢ } - //std::cout << it.family << " had score " << score << "\n"; + + //if (score > 0 && lang == "ja") { + // std::cout << it.family << " l:" << it.lang << " had score " << score << "\n"; + //} } if (best_ptr == nullptr) { diff --git a/src/font_loading/font_finder_fc_sdl.hpp b/src/font_loading/font_finder_fc_sdl.hpp index 4fa46240..db826406 100644 --- a/src/font_loading/font_finder_fc_sdl.hpp +++ b/src/font_loading/font_finder_fc_sdl.hpp @@ -7,9 +7,7 @@ #include #include #include -#include -#include // provides a fontconfig (typically linux/unix) implementation // using commandline tools using sdl process management @@ -17,11 +15,13 @@ struct FontFinder_FontConfigSDL : public FontFinderInterface { struct SystemFont { std::string family; std::string processed_family; - bool color {false}; // lang - // for emojies: - // lang: und-zsye + // for emojies: und-zsye + // langs are concatinated with | + std::string lang; + + bool color {false}; }; std::vector _cache; From ad889ee44ab8c08393abd95e134b90861aab79a1 Mon Sep 17 00:00:00 2001 From: Green Sky Date: Sat, 11 Oct 2025 22:10:46 +0200 Subject: [PATCH 6/9] add functions for default platform specific font family names and test --- src/font_loading/CMakeLists.txt | 2 ++ src/font_loading/font_finder.cpp | 62 ++++++++++++++++++++++++++++++++ src/font_loading/font_finder.hpp | 9 +++++ src/font_loading/test_ff.cpp | 34 +++++++++++++++++- 4 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 src/font_loading/font_finder.cpp create mode 100644 src/font_loading/font_finder.hpp diff --git a/src/font_loading/CMakeLists.txt b/src/font_loading/CMakeLists.txt index 465c1659..0ebbbaec 100644 --- a/src/font_loading/CMakeLists.txt +++ b/src/font_loading/CMakeLists.txt @@ -5,6 +5,8 @@ cmake_minimum_required(VERSION 3.9...3.24 FATAL_ERROR) add_library(font_loading STATIC) target_sources(font_loading PUBLIC + ./font_finder.hpp + ./font_finder.cpp ./font_finder_i.hpp ./font_finder_fs.hpp ./font_finder_fs.cpp diff --git a/src/font_loading/font_finder.cpp b/src/font_loading/font_finder.cpp new file mode 100644 index 00000000..f75cc400 --- /dev/null +++ b/src/font_loading/font_finder.cpp @@ -0,0 +1,62 @@ +#include "./font_finder.hpp" + +std::vector getPlatformDefaultUIFamilies(void) { +#if defined(_WIN32) || defined(WIN32) + return { + "Segoe UI", + "sans-serif", + "Noto Sans", + "Helvetica", + "serif", + }; +#elif __ANDROID__ + return { + "Noto Sans", + "sans-serif", + "Droid Sans", + "Roboto", + "serif", + }; +#elif __APPLE__ + return { + "Helvetica", + "sans-serif", + "Noto Sans", + "serif", + }; +#else // assume linux, prs welcome + return { + "sans-serif", + "Noto Sans", + "Ubuntu", + "serif", + }; +#endif +} + +std::vector getPlatformDefaultColorEmojiFamilies(void) { +#if defined(_WIN32) || defined(WIN32) + return { + "Seguiemj", // Segoe UI Emoji + "Color Emoji", + "Emoji", + }; +#elif __ANDROID__ + return { + "Noto Color Emoji", + "Color Emoji", + "Emoji", + }; +#elif __APPLE__ + return { + "Apple Color Emoji", + "Color Emoji", + "Emoji", + }; +#else // assume linux, other platform prs welcome + return { + "Color Emoji", + "Emoji", + }; +#endif +} diff --git a/src/font_loading/font_finder.hpp b/src/font_loading/font_finder.hpp new file mode 100644 index 00000000..5a029c4b --- /dev/null +++ b/src/font_loading/font_finder.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include +#include + +// returns a list of "family" names, that when put into the backends +// on current platform, will likely yield a match. +std::vector getPlatformDefaultUIFamilies(void); +std::vector getPlatformDefaultColorEmojiFamilies(void); diff --git a/src/font_loading/test_ff.cpp b/src/font_loading/test_ff.cpp index 2966b12a..d470c6fa 100644 --- a/src/font_loading/test_ff.cpp +++ b/src/font_loading/test_ff.cpp @@ -2,11 +2,13 @@ #include "./font_finder_fs.hpp" #include "./font_finder_fc_sdl.hpp" +#include "./font_finder.hpp" + #include #include int main(void) { - std::cout << "Font Finder Tests\n"; + std::cout << "=== Font Finder Tests ===\n"; auto fun_tests = [](FontFinderInterface& ff) { std::cout << "findBest('ColorEmoji', '', true): '" << ff.findBest("ColorEmoji", "", true) << "'\n"; @@ -36,6 +38,36 @@ int main(void) { std::cout << "findBest('sans-serif', 'ar'): '" << ff.findBest("sans-serif", "ar") << "'\n"; std::cout << "findBest('sans-serif', 'ja'): '" << ff.findBest("sans-serif", "ja") << "'\n"; std::cout << "findBest('monospace'): '" << ff.findBest("monospace") << "'\n"; + + { + std::string path; + for (const auto family : getPlatformDefaultUIFamilies()) { + path = ff.findBest(family); + if (!path.empty()) { + break; + } + } + if (!path.empty()) { + std::cout << "found getPlatformDefaultUIFamily: '" << path << "'\n"; + } else { + std::cout << "did not find getPlatformDefaultUIFamily\n"; + } + } + + { + std::string path; + for (const auto family : getPlatformDefaultColorEmojiFamilies()) { + path = ff.findBest(family); + if (!path.empty()) { + break; + } + } + if (!path.empty()) { + std::cout << "found getPlatformDefaultColorEmojiFamily: '" << path << "'\n"; + } else { + std::cout << "did not find getPlatformDefaultColorEmojiFamily\n"; + } + } }; From 4f9e2601c27b670c106ada948197e5bd681644c2 Mon Sep 17 00:00:00 2001 From: Green Sky Date: Mon, 13 Oct 2025 23:13:30 +0200 Subject: [PATCH 7/9] getbestmatch helper and more refactoring --- src/font_loading/font_finder.cpp | 66 ++++++++++++++++++++ src/font_loading/font_finder.hpp | 19 ++++++ src/font_loading/font_finder_fc_sdl.cpp | 4 ++ src/font_loading/font_finder_fc_sdl.hpp | 2 + src/font_loading/font_finder_fs.cpp | 4 ++ src/font_loading/font_finder_fs.hpp | 2 + src/font_loading/font_finder_i.hpp | 2 + src/font_loading/test_ff.cpp | 80 ++++++++++++++----------- 8 files changed, 145 insertions(+), 34 deletions(-) diff --git a/src/font_loading/font_finder.cpp b/src/font_loading/font_finder.cpp index f75cc400..8d024a06 100644 --- a/src/font_loading/font_finder.cpp +++ b/src/font_loading/font_finder.cpp @@ -1,5 +1,12 @@ #include "./font_finder.hpp" +#include "./font_finder_fc_sdl.hpp" +#include "./font_finder_fs.hpp" + +#include + +#include + std::vector getPlatformDefaultUIFamilies(void) { #if defined(_WIN32) || defined(WIN32) return { @@ -60,3 +67,62 @@ std::vector getPlatformDefaultColorEmojiFamilies(void) { }; #endif } + +std::vector> constructPlatformDefaultFinderBackends(void) { + std::vector> list; + + try { + // TODO: should we skip this on windows? + std::unique_ptr ff = std::make_unique(); + list.push_back(std::move(ff)); + } catch (const std::runtime_error& e) { + std::cerr << "caught runtime_error exception '" << e.what() << "'\n"; + } catch (...) { + std::cerr << "caught exception\n"; + } + + try { + +#if defined(_WIN32) || defined(WIN32) + std::unique_ptr ff = std::make_unique("C:\\Windows\\Fonts"); +#elif __ANDROID__ + std::unique_ptr ff = std::make_unique("/system/fonts"); +#elif __APPLE__ + // TODO: macos only + std::unique_ptr ff = std::make_unique("/System/Library/Fonts"); +#else + std::unique_ptr ff = std::make_unique("/usr/share/fonts"); +#endif + + list.push_back(std::move(ff)); + } catch (const std::runtime_error& e) { + std::cerr << "caught runtime_error exception '" << e.what() << "'\n"; + } catch (...) { + std::cerr << "caught exception\n"; + } + + return list; +} + +std::string getBestMatch( + const std::vector>& backends, + std::string_view family, + std::string_view lang, + bool color +) { + for (const auto& backend : backends) { + try { + auto res = backend->findBest(family, lang, color); + if (!res.empty()) { + return res; + } + } catch (const std::runtime_error& e) { + std::cerr << "caught runtime_error exception '" << e.what() << "'\n"; + } catch (...) { + std::cerr << "caught exception\n"; + } + } + + return ""; // none found +} + diff --git a/src/font_loading/font_finder.hpp b/src/font_loading/font_finder.hpp index 5a029c4b..b7df126a 100644 --- a/src/font_loading/font_finder.hpp +++ b/src/font_loading/font_finder.hpp @@ -1,9 +1,28 @@ #pragma once +#include "./font_finder_i.hpp" + #include #include +#include + +// for typical usage, you want to: +// 1. construct finder backends once +// 2. have a desired font family or get the family list eg. getPlatformDefaultUIFamilies() +// 3. pass it to the getBestMatch() function + // returns a list of "family" names, that when put into the backends // on current platform, will likely yield a match. std::vector getPlatformDefaultUIFamilies(void); std::vector getPlatformDefaultColorEmojiFamilies(void); + +std::vector> constructPlatformDefaultFinderBackends(void); + +std::string getBestMatch( + const std::vector>& backends, + std::string_view family, + std::string_view lang = "", + bool color = false +); + diff --git a/src/font_loading/font_finder_fc_sdl.cpp b/src/font_loading/font_finder_fc_sdl.cpp index f66503b9..c32aa633 100644 --- a/src/font_loading/font_finder_fc_sdl.cpp +++ b/src/font_loading/font_finder_fc_sdl.cpp @@ -140,6 +140,10 @@ void FontFinder_FontConfigSDL::reset(void) { fillCache(); } +const char* FontFinder_FontConfigSDL::name(void) const { + return "FontConfigSDL"; +} + // TODO: use fc-match instead, maybe fallback to this std::string FontFinder_FontConfigSDL::findBest(std::string_view family, std::string_view lang, bool color) const { const SystemFont* best_ptr = nullptr; diff --git a/src/font_loading/font_finder_fc_sdl.hpp b/src/font_loading/font_finder_fc_sdl.hpp index db826406..9a048af3 100644 --- a/src/font_loading/font_finder_fc_sdl.hpp +++ b/src/font_loading/font_finder_fc_sdl.hpp @@ -34,5 +34,7 @@ struct FontFinder_FontConfigSDL : public FontFinderInterface { void reset(void); + const char* name(void) const override; + std::string findBest(std::string_view family, std::string_view lang = "", bool color = false) const override; }; diff --git a/src/font_loading/font_finder_fs.cpp b/src/font_loading/font_finder_fs.cpp index 408e91c5..aef146b1 100644 --- a/src/font_loading/font_finder_fs.cpp +++ b/src/font_loading/font_finder_fs.cpp @@ -64,6 +64,10 @@ void FontFinder_FileSystem::addFontFile(std::string_view file_path) { }); } +const char* FontFinder_FileSystem::name(void) const { + return "FileSystem"; +} + std::string FontFinder_FileSystem::findBest(std::string_view family, std::string_view lang, bool color) const { const SystemFont* best_ptr = nullptr; int best_score = 0; diff --git a/src/font_loading/font_finder_fs.hpp b/src/font_loading/font_finder_fs.hpp index 27fa6dc8..300da88d 100644 --- a/src/font_loading/font_finder_fs.hpp +++ b/src/font_loading/font_finder_fs.hpp @@ -26,5 +26,7 @@ struct FontFinder_FileSystem : public FontFinderInterface { void addFontDir(std::string_view path); void addFontFile(std::string_view path); + const char* name(void) const override; + std::string findBest(std::string_view family, std::string_view lang = "", bool color = false) const override; }; diff --git a/src/font_loading/font_finder_i.hpp b/src/font_loading/font_finder_i.hpp index 221ed689..0f4f397b 100644 --- a/src/font_loading/font_finder_i.hpp +++ b/src/font_loading/font_finder_i.hpp @@ -5,6 +5,8 @@ struct FontFinderInterface { virtual ~FontFinderInterface(void) {} + virtual const char* name(void) const = 0; + //struct FontIteratorI { //}; //virtual FontIteratorI begin(void) const {} diff --git a/src/font_loading/test_ff.cpp b/src/font_loading/test_ff.cpp index d470c6fa..1b3cb41b 100644 --- a/src/font_loading/test_ff.cpp +++ b/src/font_loading/test_ff.cpp @@ -1,16 +1,55 @@ #include "./font_finder_i.hpp" -#include "./font_finder_fs.hpp" -#include "./font_finder_fc_sdl.hpp" #include "./font_finder.hpp" #include -#include + +// https://youtu.be/zOVuhrg-4ns int main(void) { - std::cout << "=== Font Finder Tests ===\n"; + std::cout << "### Font Finder Tests ###\n"; + + std::cout << "=== Construction of Backends ===\n"; + + std::vector> backends; + backends = constructPlatformDefaultFinderBackends(); - auto fun_tests = [](FontFinderInterface& ff) { + std::cout << "=== getBestMatch ===\n"; + + { + std::string path; + for (const auto family : getPlatformDefaultUIFamilies()) { + path = getBestMatch(backends, family); + if (!path.empty()) { + break; + } + } + if (!path.empty()) { + std::cout << "found getPlatformDefaultUIFamily: '" << path << "'\n"; + } else { + std::cout << "did not find getPlatformDefaultUIFamily\n"; + } + } + { + std::string path; + for (const auto family : getPlatformDefaultColorEmojiFamilies()) { + path = getBestMatch(backends, family, "", true); + if (!path.empty()) { + break; + } + } + if (!path.empty()) { + std::cout << "found getPlatformDefaultColorEmojiFamily: '" << path << "'\n"; + } else { + std::cout << "did not find getPlatformDefaultColorEmojiFamily\n"; + } + } + + std::cout << "=== Extra tests ===\n"; + + for (const auto& backend : backends) { + const auto& ff = *backend; + std::cout << "--- " << backend->name() << " ---" << "\n"; std::cout << "findBest('ColorEmoji', '', true): '" << ff.findBest("ColorEmoji", "", true) << "'\n"; std::cout << "findBest('Emoji', '', true): '" << ff.findBest("Emoji", "", true) << "'\n"; std::cout << "findBest('Emoji', '', false): '" << ff.findBest("Emoji", "", false) << "'\n"; @@ -19,6 +58,7 @@ int main(void) { std::cout << "findBest('Noto Color Emoji'): '" << ff.findBest("Noto Color Emoji") << "'\n"; std::cout << "findBest('NotoSans'): '" << ff.findBest("NotoSans") << "'\n"; std::cout << "findBest('Noto Sans'): '" << ff.findBest("Noto Sans") << "'\n"; + std::cout << "findBest('Ubuntu'): '" << ff.findBest("Ubuntu") << "'\n"; // windows file name std::cout << "findBest('Seguiemj'): '" << ff.findBest("Seguiemj") << "'\n"; @@ -57,7 +97,7 @@ int main(void) { { std::string path; for (const auto family : getPlatformDefaultColorEmojiFamilies()) { - path = ff.findBest(family); + path = ff.findBest(family, "", true); if (!path.empty()) { break; } @@ -68,34 +108,6 @@ int main(void) { std::cout << "did not find getPlatformDefaultColorEmojiFamily\n"; } } - }; - - - std::cout << std::string(20, '=') << " FCSDL " << std::string(20, '=') << "\n"; - try { - FontFinder_FontConfigSDL ff; - fun_tests(ff); - } catch (...) { - std::cerr << "caught exception\n"; - } - - std::cout << std::string(20, '=') << " FS " << std::string(20, '=') << "\n"; - try { -#if defined(_WIN32) || defined(WIN32) - FontFinder_FileSystem ff{"C:\\Windows\\Fonts"}; -#elif __ANDROID__ - FontFinder_FileSystem ff{"/system/fonts"}; -#elif __APPLE__ - // TODO: macos only - FontFinder_FileSystem ff{"/System/Library/Fonts"}; -#else - FontFinder_FileSystem ff{"/usr/share/fonts"}; -#endif - fun_tests(ff); - } catch (const std::runtime_error& e) { - std::cerr << "caught runtime_error exception '" << e.what() << "'\n"; - } catch (...) { - std::cerr << "caught exception\n"; } return 0; From c74a4a0d8c1ef1e386b540dc56c40f1946fdf1da Mon Sep 17 00:00:00 2001 From: Green Sky Date: Tue, 14 Oct 2025 23:25:47 +0200 Subject: [PATCH 8/9] automatic font finding and loading --- src/start_screen.cpp | 48 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/src/start_screen.cpp b/src/start_screen.cpp index 0dc54ffc..6a05d6ef 100644 --- a/src/start_screen.cpp +++ b/src/start_screen.cpp @@ -6,6 +6,8 @@ #include "./chat_gui/about.hpp" +#include "./font_loading/font_finder.hpp" + #include #include @@ -84,7 +86,7 @@ StartScreen::StartScreen(const std::vector& args, SDL_Renderer ImGui::GetIO().FontGlobalScale = display_scale; ImGui::GetStyle().FontSizeBase = _conf.get_int("ImGuiFonts", "size").value_or(13); - { + if constexpr (false) { auto* font_atlas = ImGui::GetIO().Fonts; font_atlas->ClearFonts(); // for now we also always merge @@ -139,6 +141,50 @@ StartScreen::StartScreen(const std::vector& args, SDL_Renderer } font_atlas->Build(); + } else { + auto* font_atlas = ImGui::GetIO().Fonts; + font_atlas->ClearFonts(); + + const auto ff_backends = constructPlatformDefaultFinderBackends(); + + // last text font is always built-in + font_atlas->AddFontDefault(); + + { // emojies after text + ImFontConfig fontcfg_emoji; + fontcfg_emoji.MergeMode = true; + const auto emoji_families = getPlatformDefaultColorEmojiFamilies(); + + std::string font_path; + +#if defined(IMGUI_ENABLE_FREETYPE) + #if defined(IMGUI_ENABLE_FREETYPE_PLUTOSVG) + std::cout << "Font: enabling freetype color loading\n"; + fontcfg_emoji.FontLoaderFlags |= ImGuiFreeTypeBuilderFlags_LoadColor; + for (const auto family : emoji_families) { + font_path = getBestMatch(ff_backends, family, "", true); + if (!font_path.empty()) { + break; + } + } + #else + // fallback to monochrome + for (const auto family : emoji_families) { + font_path = getBestMatch(ff_backends, family); + if (!font_path.empty()) { + break; + } + } + #endif + // ??? + fontcfg_emoji.FontLoaderFlags |= ImGuiFreeTypeBuilderFlags_Bitmap; +#endif + + if (font_atlas->AddFontFromFileTTF(font_path.c_str(), 0.f, &fontcfg_emoji) == nullptr) { + std::cerr << "Font error: failed adding '" << font_path << "' to font atlas\n"; + } + } + } if (config_loaded) { // pull plugins from config From 38c207436a09c17d9e26fb6652f49817fcd15b0f Mon Sep 17 00:00:00 2001 From: Green Sky Date: Sat, 1 Nov 2025 18:12:47 +0100 Subject: [PATCH 9/9] testing imgui stuff --- external/imgui/CMakeLists.txt | 4 +- src/chat_gui4.cpp | 5 +- src/main_screen.cpp | 19 +++++++ src/start_screen.cpp | 95 ++++++++++++++++++++++++++++++----- 4 files changed, 108 insertions(+), 15 deletions(-) diff --git a/external/imgui/CMakeLists.txt b/external/imgui/CMakeLists.txt index 5423efdc..7774e960 100644 --- a/external/imgui/CMakeLists.txt +++ b/external/imgui/CMakeLists.txt @@ -14,7 +14,9 @@ if (NOT TARGET imgui) #GIT_TAG 85b2fe8486190fa9326565a2fb5fccb6caea4396 # v1.92.0 #GIT_TAG 5d4126876bc10396d4c6511853ff10964414c776 # v1.92.1 #GIT_TAG bf75bfec48fc00f532af8926130b70c0e26eb099 # v1.92.3 - GIT_TAG 349dbf9c57a15e2148fbfa7cb88df30280e0a362 # v1.92.3 + bitmap scaling patches + #GIT_TAG 349dbf9c57a15e2148fbfa7cb88df30280e0a362 # v1.92.3 + bitmap scaling patches + #GIT_TAG bitmaps_and_emsize + GIT_TAG fixed_sized_bitmaps_density1 EXCLUDE_FROM_ALL ) diff --git a/src/chat_gui4.cpp b/src/chat_gui4.cpp index afd7e985..0aef76b1 100644 --- a/src/chat_gui4.cpp +++ b/src/chat_gui4.cpp @@ -483,8 +483,9 @@ float ChatGui4::render(float time_delta, bool window_hidden, bool window_focused constexpr ImGuiInputTextFlags input_flags = //ImGuiInputTextFlags_AllowTabInput | ImGuiInputTextFlags_NoHorizontalScroll | - ImGuiInputTextFlags_CallbackCharFilter | - ImGuiInputTextFlags_WordWrap; + ImGuiInputTextFlags_CallbackCharFilter + //ImGuiInputTextFlags_WordWrap + ; bool text_input_validate {false}; ImGui::InputTextMultiline( diff --git a/src/main_screen.cpp b/src/main_screen.cpp index 4ed49fae..912589d3 100644 --- a/src/main_screen.cpp +++ b/src/main_screen.cpp @@ -479,6 +479,25 @@ Screen* MainScreen::render(float time_delta, bool&) { ImGui::End(); } + if (ImGui::Begin("emoji debug")) { + ImGui::BeginTable("font display", 2, ImGuiTableFlags_BordersInner); + ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthFixed); + for (int i = 26; i <= 32; i++) { + ImGui::TableNextColumn(); + ImGui::Text("%d", i); + if (i == 26 || i == 32) { + ImGui::SameLine(); + ImGui::Text("base"); + } + ImGui::TableNextColumn(); + ImGui::PushFont(nullptr, i); + ImGui::Text(u8"TšŸ˜‚ā¤ļøšŸ¤£šŸ‘šŸ…šŸ„°šŸŒ“"); + ImGui::PopFont(); + } + ImGui::EndTable(); + } + ImGui::End(); + _compute_lower_limit_hit_rendered = true; float tc_unfinished_queue_interval; diff --git a/src/start_screen.cpp b/src/start_screen.cpp index 6a05d6ef..0b040b3d 100644 --- a/src/start_screen.cpp +++ b/src/start_screen.cpp @@ -141,26 +141,92 @@ StartScreen::StartScreen(const std::vector& args, SDL_Renderer } font_atlas->Build(); - } else { + } else if (true) { + ImGui::GetStyle().FontSizeBase = _conf.get_int("ImGuiFonts", "size").value_or(13); + auto* font_atlas = ImGui::GetIO().Fonts; + ImGui::GetIO().FontGlobalScale = display_scale; font_atlas->ClearFonts(); const auto ff_backends = constructPlatformDefaultFinderBackends(); + ImFontConfig fontcfg; + + // defaults + fontcfg.OversampleH = fontcfg.OversampleV = 1; + fontcfg.PixelSnapH = true; + + //if (font_atlas->AddFontFromFileTTF("/home/green/Downloads/msgothic.ttc", 0.f, &fontcfg)) { + // fontcfg.MergeMode = true; + // std::cout << "Font: added msgothic.ttc\n"; + //} + + if constexpr (false) { + const auto ui_families = getPlatformDefaultUIFamilies(); + + auto findUIPath = [&ff_backends](const auto& families, std::string_view lang) { + std::string font_path; + for (const auto family : families) { + font_path = getBestMatch(ff_backends, family, lang); + if (!font_path.empty()) { + break; + } + } + return font_path; + }; + + { + const std::string font_path = findUIPath(ui_families, ""); + if (!font_path.empty()) { + auto* font = font_atlas->AddFontFromFileTTF(font_path.c_str(), 0.f, &fontcfg); + if (font == nullptr) { + std::cerr << "Font error: failed adding '" << font_path << "' to font atlas\n"; + } + } else { + std::cerr << "FontFinding error: unable to find base ui font; tried families: '"; + for (const auto f : ui_families) { + std::cerr << f << ","; + } + std::cerr << "'\n"; + } + } + + if (!font_atlas->Fonts.empty()) { + fontcfg.MergeMode = true; // always after first one + } + + // TODO: figure out a good default for extra langs + { + const std::string font_path = findUIPath(ui_families, "ja"); + if (!font_path.empty()) { + auto* font = font_atlas->AddFontFromFileTTF(font_path.c_str(), 0.f, &fontcfg); + if (font == nullptr) { + std::cerr << "Font error: failed adding '" << font_path << "' to font atlas\n"; + } + } else { + std::cerr << "FontFinding error: unable to find cjk ui font\n"; + } + } + + if (!font_atlas->Fonts.empty()) { + fontcfg.MergeMode = true; // always after first one + } + } + // last text font is always built-in - font_atlas->AddFontDefault(); + font_atlas->AddFontDefault(&fontcfg); - { // emojies after text - ImFontConfig fontcfg_emoji; - fontcfg_emoji.MergeMode = true; + if constexpr (true) { // emojies after text + fontcfg.MergeMode = true; const auto emoji_families = getPlatformDefaultColorEmojiFamilies(); std::string font_path; + // TODO: also enable loadcolor + bitmap generally?? #if defined(IMGUI_ENABLE_FREETYPE) #if defined(IMGUI_ENABLE_FREETYPE_PLUTOSVG) std::cout << "Font: enabling freetype color loading\n"; - fontcfg_emoji.FontLoaderFlags |= ImGuiFreeTypeBuilderFlags_LoadColor; + fontcfg.FontLoaderFlags |= ImGuiFreeTypeBuilderFlags_LoadColor; for (const auto family : emoji_families) { font_path = getBestMatch(ff_backends, family, "", true); if (!font_path.empty()) { @@ -176,15 +242,20 @@ StartScreen::StartScreen(const std::vector& args, SDL_Renderer } } #endif - // ??? - fontcfg_emoji.FontLoaderFlags |= ImGuiFreeTypeBuilderFlags_Bitmap; + // this forces bitmap -> imgui layout bugs ( + fontcfg.FontLoaderFlags |= ImGuiFreeTypeBuilderFlags_Bitmap; #endif - - if (font_atlas->AddFontFromFileTTF(font_path.c_str(), 0.f, &fontcfg_emoji) == nullptr) { - std::cerr << "Font error: failed adding '" << font_path << "' to font atlas\n"; + if (!font_path.empty()) { + auto* emoji_font = font_atlas->AddFontFromFileTTF(font_path.c_str(), 0.f, &fontcfg); + if (emoji_font == nullptr) { + std::cerr << "Font error: failed adding '" << font_path << "' to font atlas\n"; + } else { + std::cout << "Font: adding '" << font_path << "' to font atlas\n"; + } + } else { + std::cerr << "FontFinding error: unable to find emoji font\n"; } } - } if (config_loaded) { // pull plugins from config