diff --git a/.gitmodules b/.gitmodules index 55ae48ea3..69876af41 100644 --- a/.gitmodules +++ b/.gitmodules @@ -119,6 +119,10 @@ [submodule "externals/aacdec/fdk-aac"] path = externals/aacdec/fdk-aac url = https://android.googlesource.com/platform/external/aac +[submodule "externals/freetype"] + path = externals/freetype + url = https://github.com/freetype/freetype.git + shallow = true [submodule "externals/CLI11"] path = externals/CLI11 url = https://github.com/shadexternals/CLI11.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 147903a7e..7c1c921ef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -486,8 +486,12 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.cpp src/core/libraries/web_browser_dialog/webbrowserdialog.h src/core/libraries/font/font.cpp src/core/libraries/font/font.h + src/core/libraries/font/font_internal.cpp + src/core/libraries/font/font_internal.h src/core/libraries/font/fontft.cpp src/core/libraries/font/fontft.h + src/core/libraries/font/fontft_internal.cpp + src/core/libraries/font/fontft_internal.h src/core/libraries/font/font_error.h ) @@ -1137,7 +1141,7 @@ add_executable(shadps4 create_target_directory_groups(shadps4) -target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui gcn half::half ZLIB::ZLIB PNG::PNG) +target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui gcn half::half ZLIB::ZLIB PNG::PNG freetype) target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::glslang SDL3::SDL3 SDL3_mixer::SDL3_mixer pugixml::pugixml) target_link_libraries(shadps4 PRIVATE stb::headers lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json miniz::miniz fdk-aac CLI11::CLI11 OpenAL::OpenAL Cpp_Httplib) diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 9e19e1404..7b3c82312 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -56,6 +56,16 @@ if (NOT TARGET ZLIB::ZLIB) set(ZLIB_INCLUDE_DIRS "${FETCHCONTENT_BASE_DIR}/zlib-build") endif() +# FreeType +if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/freetype/CMakeLists.txt" AND NOT TARGET freetype) + # Keep optional deps off to reduce build surface; zlib stays enabled for compressed fonts. + set(FT_DISABLE_BZIP2 ON CACHE BOOL "" FORCE) + set(FT_DISABLE_BROTLI ON CACHE BOOL "" FORCE) + set(FT_DISABLE_HARFBUZZ ON CACHE BOOL "" FORCE) + set(FT_DISABLE_PNG ON CACHE BOOL "" FORCE) + add_subdirectory(freetype) +endif() + # SDL3 if (NOT TARGET SDL3::SDL3) set(SDL_TEST_LIBRARY OFF) diff --git a/externals/freetype b/externals/freetype new file mode 160000 index 000000000..b91f75bd0 --- /dev/null +++ b/externals/freetype @@ -0,0 +1 @@ +Subproject commit b91f75bd02db43b06d634591eb286d3eb0ce3b65 diff --git a/src/core/libraries/font/font.cpp b/src/core/libraries/font/font.cpp index 1454004aa..ac638c70e 100644 --- a/src/core/libraries/font/font.cpp +++ b/src/core/libraries/font/font.cpp @@ -1,28 +1,288 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include FT_FREETYPE_H +#include FT_TRUETYPE_TABLES_H +#include "common/config.h" #include "common/logging/log.h" +#include "core/file_sys/fs.h" #include "core/libraries/error_codes.h" #include "core/libraries/font/font.h" +#include "core/libraries/font/font_internal.h" +#include "core/libraries/font/fontft.h" +#include "core/libraries/font/fontft_internal.h" +#include "core/libraries/kernel/kernel.h" +#include "core/libraries/kernel/process.h" +#include "core/libraries/kernel/time.h" #include "core/libraries/libs.h" +#include "core/memory.h" +#include "core/tls.h" #include "font_error.h" +#ifdef formatParams +#undef formatParams +#endif + +namespace { +using Libraries::Font::OrbisFontGenerateGlyphParams; +using Libraries::Font::OrbisFontGlyph; +using Libraries::Font::OrbisFontGlyphMetrics; +using Libraries::Font::OrbisFontGlyphOpaque; +using Libraries::Font::OrbisFontGlyphOutline; +using Libraries::Font::OrbisFontGlyphOutlinePoint; +using Libraries::Font::OrbisFontMem; +using Libraries::Font::OrbisFontStyleFrame; +using Libraries::Font::Internal::Param; + +static std::string formatParams( + std::initializer_list params) { + return Libraries::Font::Internal::FormatNamedParams(params); +} + +} // namespace + +namespace Internal = Libraries::Font::Internal; + namespace Libraries::Font { -s32 PS4_SYSV_ABI sceFontAttachDeviceCacheBuffer() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); - return ORBIS_OK; +using Internal::AcquireCachedStyleLock; +using Internal::AcquireFontLock; +using Internal::AcquireLibraryLock; +using Internal::CachedStyleCacheFlags; +using Internal::CachedStyleGetScalar; +using Internal::CachedStyleSetCacheFlags; +using Internal::CachedStyleSetDirectionWord; +using Internal::CachedStyleSetScalar; +using Internal::ClampToU16; +using Internal::ClearRenderOutputs; +using Internal::GetLibState; +using Internal::GetNativeFont; +using Internal::kPointsPerInch; +using Internal::kStyleFrameMagic; +using Internal::LoadGuestFileBytes; +using Internal::LogCachedStyleOnce; +using Internal::LogFontOpenError; +using Internal::LogRenderResultSample; +using Internal::ReleaseCachedStyleLock; +using Internal::ReleaseFontLock; +using Internal::ReleaseLibraryLock; +using Internal::RemoveLibState; +using Internal::ResolveGuestPath; +using Internal::ResolveKnownSysFontAlias; + +constexpr std::uintptr_t kFtRendererCreateFnAddr = 0x0100f6d0u; +constexpr std::uintptr_t kFtRendererDestroyFnAddr = 0x0100f790u; + +s32 PS4_SYSV_ABI sceFontAttachDeviceCacheBuffer(OrbisFontLib library, void* buffer, u32 size) { + LOG_INFO(Lib_Font, "called"); + LOG_DEBUG(Lib_Font, "{}", + formatParams({ + Param("library", library), + Param("buffer", buffer), + Param("size", size), + })); + + if (!library) { + LOG_ERROR(Lib_Font, "INVALID_LIBRARY"); + return ORBIS_FONT_ERROR_INVALID_LIBRARY; + } + auto* lib = static_cast(library); + if (lib->magic != 0x0F01) { + LOG_ERROR(Lib_Font, "INVALID_LIBRARY"); + return ORBIS_FONT_ERROR_INVALID_LIBRARY; + } + + u32 prev_lock_word = 0; + if (!AcquireLibraryLock(lib, prev_lock_word)) { + LOG_ERROR(Lib_Font, "INVALID_LIBRARY"); + return ORBIS_FONT_ERROR_INVALID_LIBRARY; + } + + const auto kBusy = reinterpret_cast( + static_cast(std::numeric_limits::max())); + + u32* current_cache = nullptr; + for (;;) { + current_cache = lib->device_cache_buf; + if (current_cache != kBusy) { + std::atomic_ref ref(lib->device_cache_buf); + u32* expected = current_cache; + if (ref.compare_exchange_weak(expected, kBusy, std::memory_order_acq_rel)) { + current_cache = expected; + break; + } + } + Libraries::Kernel::sceKernelUsleep(0x1e); + } + + const auto alloc_fn = + lib->alloc_vtbl ? reinterpret_cast(lib->alloc_vtbl[0]) : nullptr; + const auto free_fn = + lib->alloc_vtbl ? reinterpret_cast(lib->alloc_vtbl[1]) : nullptr; + (void)alloc_fn; + (void)free_fn; + + s32 rc = ORBIS_FONT_ERROR_INVALID_PARAMETER; + u32* cache_to_store = current_cache; + u32 owned = 0; + + if (current_cache != nullptr) { + rc = ORBIS_FONT_ERROR_ALREADY_ATTACHED; + } else if (size < 0x1020) { + cache_to_store = nullptr; + rc = ORBIS_FONT_ERROR_INVALID_PARAMETER; + } else { + u32* header = static_cast(buffer); + if (!header) { + header = static_cast(alloc_fn(lib->alloc_ctx, size)); + if (!header) { + cache_to_store = nullptr; + rc = ORBIS_FONT_ERROR_ALLOCATION_FAILED; + } else { + owned = 1; + } + } + + if (rc == ORBIS_FONT_ERROR_INVALID_PARAMETER) { + header[0] = size; + const u32 page_count = ((size - 0x1000) >> 12); + header[1] = page_count; + header[3] = page_count; + header[2] = 0; + *reinterpret_cast(header + 4) = 0x0FF800001000ULL; + + if (0x0FFF < (size - 0x1000)) { + lib->flags |= owned; + rc = ORBIS_OK; + cache_to_store = header; + if (owned) { + auto& state = GetLibState(library); + state.alloc_ctx = lib->alloc_ctx; + state.alloc_fn = alloc_fn; + state.free_fn = free_fn; + state.owned_device_cache = header; + state.owned_device_cache_size = size; + } + } else { + if (!buffer) { + free_fn(lib->alloc_ctx, header); + } + cache_to_store = nullptr; + rc = ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + } + } + + lib->device_cache_buf = cache_to_store; + ReleaseLibraryLock(lib, prev_lock_word); + if (rc != ORBIS_OK) { + LOG_ERROR(Lib_Font, "FAILED"); + } + return rc; } -s32 PS4_SYSV_ABI sceFontBindRenderer() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceFontBindRenderer(OrbisFontHandle fontHandle, OrbisFontRenderer renderer) { + LOG_INFO(Lib_Font, "called"); + LOG_DEBUG(Lib_Font, "{}", + formatParams({ + Param("fontHandle", fontHandle), + Param("renderer", renderer), + })); + + auto* font = GetNativeFont(fontHandle); + if (!font) { + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + u32 prev_font_lock = 0; + if (!AcquireFontLock(font, prev_font_lock)) { + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + u32 prev_cached_lock = 0; + if (!AcquireCachedStyleLock(font, prev_cached_lock)) { + ReleaseFontLock(font, prev_font_lock); + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + s32 rc = ORBIS_FONT_ERROR_ALREADY_BOUND_RENDERER; + if (font->renderer_binding.renderer == nullptr) { + rc = ORBIS_FONT_ERROR_INVALID_RENDERER; + if (renderer && *reinterpret_cast(renderer) == 0x0F07) { + rc = ORBIS_OK; + + std::memcpy(&font->cached_style.effectWeightY, &font->style_tail.slant_ratio, + sizeof(u64)); + + std::memcpy(&font->cached_style, &font->style_frame[0], sizeof(u32)); + + const u32 dpi_y = font->style_frame[1]; + const u32 scale_unit = font->style_tail.scale_unit; + const u32 reserved_0x0c = font->style_tail.reserved_0x0c; + const float scale_w = font->style_tail.scale_w; + const float scale_h = font->style_tail.scale_h; + const float effect_weight_x = font->style_tail.effect_weight_x; + const float effect_weight_y = font->style_tail.effect_weight_y; + + font->cached_style.hDpi = dpi_y; + font->cached_style.vDpi = scale_unit; + font->cached_style.scaleUnit = reserved_0x0c; + font->cached_style.baseScale = scale_w; + font->cached_style.scalePixelW = scale_h; + font->cached_style.scalePixelH = effect_weight_x; + font->cached_style.effectWeightX = effect_weight_y; + + font->renderer_binding.renderer = renderer; + } + } + + ReleaseCachedStyleLock(font, prev_cached_lock); + ReleaseFontLock(font, prev_font_lock); + if (rc != ORBIS_OK) { + if (rc == ORBIS_FONT_ERROR_ALREADY_BOUND_RENDERER) { + LOG_ERROR(Lib_Font, "ALREADY_BOUND"); + } else if (rc == ORBIS_FONT_ERROR_INVALID_RENDERER) { + LOG_ERROR(Lib_Font, "INVALID_RENDERER"); + } else { + LOG_ERROR(Lib_Font, "FAILED"); + } + } + return rc; } s32 PS4_SYSV_ABI sceFontCharacterGetBidiLevel(OrbisFontTextCharacter* textCharacter, int* bidiLevel) { if (!textCharacter || !bidiLevel) { - LOG_DEBUG(Lib_Font, "Invalid parameter"); + LOG_ERROR(Lib_Font, "invalid parameter"); return ORBIS_FONT_ERROR_INVALID_PARAMETER; } @@ -43,17 +303,16 @@ s32 PS4_SYSV_ABI sceFontCharacterGetTextFontCode() { s32 PS4_SYSV_ABI sceFontCharacterGetTextOrder(OrbisFontTextCharacter* textCharacter, void** pTextOrder) { if (!pTextOrder) { - LOG_DEBUG(Lib_Font, "Invalid parameter"); + LOG_ERROR(Lib_Font, "invalid parameter (pTextOrder)"); return ORBIS_FONT_ERROR_INVALID_PARAMETER; } if (!textCharacter) { - LOG_DEBUG(Lib_Font, "Invalid parameter"); + LOG_ERROR(Lib_Font, "invalid parameter (textCharacter)"); *pTextOrder = NULL; return ORBIS_FONT_ERROR_INVALID_PARAMETER; } - // Retrieve text order *pTextOrder = textCharacter->textOrder; return ORBIS_OK; } @@ -63,7 +322,6 @@ u32 PS4_SYSV_ABI sceFontCharacterLooksFormatCharacters(OrbisFontTextCharacter* t return 0; } - // Check if the format flag (bit 2) is set return (textCharacter->formatFlags & 0x04) ? textCharacter->characterCode : 0; } @@ -78,33 +336,33 @@ u32 PS4_SYSV_ABI sceFontCharacterLooksWhiteSpace(OrbisFontTextCharacter* textCha OrbisFontTextCharacter* PS4_SYSV_ABI sceFontCharacterRefersTextBack(OrbisFontTextCharacter* textCharacter) { if (!textCharacter) - return NULL; // Check if input is NULL + return NULL; - OrbisFontTextCharacter* current = textCharacter->prev; // Move backward instead of forward + OrbisFontTextCharacter* current = textCharacter->prev; while (current) { - if (current->unkn_0x31 == 0 && current->unkn_0x33 == 0) { - return current; // Return the first matching node + if (current->unknown_0x31 == 0 && current->unknown_0x33 == 0) { + return current; } - current = current->prev; // Move to the previous node + current = current->prev; } - return NULL; // No valid node found + return NULL; } OrbisFontTextCharacter* PS4_SYSV_ABI sceFontCharacterRefersTextNext(OrbisFontTextCharacter* textCharacter) { if (!textCharacter) - return NULL; // Null check + return NULL; OrbisFontTextCharacter* current = textCharacter->next; while (current) { - if (current->unkn_0x31 == 0 && current->unkn_0x33 == 0) { - return current; // Found a match + if (current->unknown_0x31 == 0 && current->unknown_0x33 == 0) { + return current; } - current = current->next; // Move to the next node + current = current->next; } - return NULL; // No matching node found + return NULL; } s32 PS4_SYSV_ABI sceFontCharactersRefersTextCodes() { @@ -112,9 +370,48 @@ s32 PS4_SYSV_ABI sceFontCharactersRefersTextCodes() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontClearDeviceCache() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceFontClearDeviceCache(OrbisFontLib library) { + if (!library) { + return ORBIS_FONT_ERROR_INVALID_LIBRARY; + } + + auto* lib = static_cast(library); + if (lib->magic != 0x0F01) { + return ORBIS_FONT_ERROR_INVALID_LIBRARY; + } + + u32 prev_lock_word = 0; + if (!AcquireLibraryLock(lib, prev_lock_word)) { + return ORBIS_FONT_ERROR_INVALID_LIBRARY; + } + + const auto kBusy = reinterpret_cast( + static_cast(std::numeric_limits::max())); + + u32* current_cache = nullptr; + for (;;) { + current_cache = lib->device_cache_buf; + if (current_cache != kBusy) { + std::atomic_ref ref(lib->device_cache_buf); + u32* expected = current_cache; + if (ref.compare_exchange_weak(expected, kBusy, std::memory_order_acq_rel)) { + current_cache = expected; + break; + } + } + Libraries::Kernel::sceKernelUsleep(0x1e); + } + + s32 rc = ORBIS_FONT_ERROR_NOT_ATTACHED_CACHE_BUFFER; + if (current_cache != nullptr) { + current_cache[3] = current_cache[1]; + current_cache[2] = 0; + rc = ORBIS_OK; + } + + lib->device_cache_buf = current_cache; + ReleaseLibraryLock(lib, prev_lock_word); + return rc; } s32 PS4_SYSV_ABI sceFontCloseFont() { @@ -142,24 +439,340 @@ s32 PS4_SYSV_ABI sceFontCreateGraphicsServiceWithEdition() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontCreateLibrary() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceFontCreateLibrary(const OrbisFontMem* memory, + OrbisFontLibCreateParams create_params, + OrbisFontLib* pLibrary) { + return sceFontCreateLibraryWithEdition(memory, create_params, 0, pLibrary); +} + +s32 PS4_SYSV_ABI sceFontCreateLibraryWithEdition(const OrbisFontMem* memory, + OrbisFontLibCreateParams create_params, + u64 edition, OrbisFontLib* pLibrary) { + LOG_INFO(Lib_Font, "called"); + LOG_DEBUG(Lib_Font, "{}", + formatParams({ + Param("memory", memory), + Param("create_params", create_params), + Param("edition", edition), + Param("pLibrary", pLibrary), + })); + + if (pLibrary) { + *pLibrary = nullptr; + } + if (!memory) { + LOG_ERROR(Lib_Font, "NULL_POINTER"); + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + if (memory->mem_kind != 0x0F00 || !memory->iface || !memory->iface->alloc || + !memory->iface->dealloc) { + LOG_ERROR(Lib_Font, "INVALID_MEMORY"); + return ORBIS_FONT_ERROR_INVALID_MEMORY; + } + if (!create_params || !pLibrary) { + LOG_ERROR(Lib_Font, "INVALID_PARAMETER"); + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + + const auto malloc_fn = reinterpret_cast(memory->iface->alloc); + const auto free_fn = reinterpret_cast(memory->iface->dealloc); + if (!malloc_fn || !free_fn) { + LOG_ERROR(Lib_Font, "INVALID_MEMORY"); + return ORBIS_FONT_ERROR_INVALID_MEMORY; + } + + void* lib_mem = malloc_fn(memory->mspace_handle, 0x100); + if (!lib_mem) { + LOG_ERROR(Lib_Font, "ALLOCATION_FAILED"); + return ORBIS_FONT_ERROR_ALLOCATION_FAILED; + } + + void* mspace = malloc_fn(memory->mspace_handle, 0x4000); + if (!mspace) { + free_fn(memory->mspace_handle, lib_mem); + LOG_ERROR(Lib_Font, "ALLOCATION_FAILED"); + return ORBIS_FONT_ERROR_ALLOCATION_FAILED; + } + + auto* lib_bytes = static_cast(lib_mem); + auto clear_20 = [&](std::size_t offset) { std::memset(lib_bytes + offset, 0, 0x20); }; + clear_20(0x00); + clear_20(sizeof(Internal::FontLibOpaque) + offsetof(Internal::FontLibOpaque, alloc_vtbl)); + clear_20(sizeof(Internal::FontLibOpaque) + offsetof(Internal::FontLibOpaque, flags)); + clear_20(offsetof(Internal::FontLibOpaque, sysfonts_ctx)); + clear_20(offsetof(Internal::FontLibOpaque, sys_driver)); + clear_20(offsetof(Internal::FontLibOpaque, reserved2) + 0x30); + clear_20(offsetof(Internal::FontLibOpaque, reserved2) + 0x10); + clear_20(offsetof(Internal::FontLibOpaque, alloc_ctx)); + clear_20(offsetof(Internal::FontLibOpaque, reserved3) + 0x0C); + clear_20(offsetof(Internal::FontLibOpaque, fontset_registry)); + + auto* lib = static_cast(lib_mem); + + auto* reserved1 = reinterpret_cast(lib->reserved1); + if (reserved1) { + reserved1->mem_kind = 0x0F00; + reserved1->region_size = memory->region_size; + reserved1->region_base = memory->region_base; + } + + lib->alloc_ctx = memory->mspace_handle; + lib->alloc_vtbl = reinterpret_cast(const_cast(memory->iface)); + + auto* reserved2 = reinterpret_cast(lib->reserved2); + if (reserved2) { + reserved2->alloc_fn = reinterpret_cast(memory->iface->alloc); + reserved2->dealloc_fn = reinterpret_cast(memory->iface->dealloc); + reserved2->realloc_fn = reinterpret_cast(memory->iface->realloc_fn); + reserved2->calloc_fn = reinterpret_cast(memory->iface->calloc_fn); + } + + lib->sys_driver = const_cast(create_params); + + auto* tail = reinterpret_cast(lib + 1); + if (tail) { + tail->workspace_size = 0x4000; + tail->workspace = mspace; + tail->list_head_ptr = &tail->list_head; + tail->list_head = nullptr; + } + + const auto* sys_driver = reinterpret_cast(create_params); + const auto init_fn = sys_driver ? sys_driver->init : nullptr; + if (!init_fn) { + LOG_ERROR(Lib_Font, "INVALID_PARAMETER"); + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + + const s32 init_rc = init_fn(memory, lib); + if (init_rc != ORBIS_OK) { + free_fn(memory->mspace_handle, mspace); + free_fn(memory->mspace_handle, lib_mem); + LOG_ERROR(Lib_Font, "INIT_FAILED"); + return init_rc; + } + + s32 sdk_version = 0; + u32 sdk_version_u32 = 0; + if (Libraries::Kernel::sceKernelGetCompiledSdkVersion(&sdk_version) == ORBIS_OK) { + sdk_version_u32 = static_cast(sdk_version); + } + const u32 edition_hi = static_cast(edition >> 32); + u32 v = edition_hi; + if (sdk_version_u32 != 0) { + v = sdk_version_u32; + if (edition_hi <= sdk_version_u32) { + v = edition_hi; + } + } + if (0x92ffffu < v) { + auto* pad_00A_01E = lib->reserved1; + auto* feature_word = reinterpret_cast(pad_00A_01E); + if (v < 0x1500000u) { + u32 tmp = (*feature_word) | 1u; + *feature_word = tmp; + tmp |= 2u; + *feature_word = tmp; + *feature_word = tmp | 4u; + } else if (v < 0x2000000u) { + u32 tmp = *feature_word; + tmp |= 2u; + *feature_word = tmp; + *feature_word = tmp | 4u; + } else if (v <= 0x2000070u) { + u32 tmp = *feature_word; + *feature_word = tmp | 4u; + } + } + + lib->magic = 0x0F01; + + *pLibrary = lib; + auto& state = GetLibState(lib); + state = Internal::LibraryState{}; + state.backing_memory = memory; + state.create_params = create_params; + state.edition = edition; + state.alloc_ctx = lib->alloc_ctx; + state.alloc_fn = reinterpret_cast(lib->alloc_vtbl[0]); + state.free_fn = reinterpret_cast(lib->alloc_vtbl[1]); + state.owned_mspace = mspace; + state.owned_mspace_size = 0x4000; return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontCreateLibraryWithEdition() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceFontDestroyLibrary(OrbisFontLib* pLibrary) { + if (!pLibrary) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + auto lib = *pLibrary; + if (!lib) { + return ORBIS_FONT_ERROR_INVALID_LIBRARY; + } + LOG_INFO(Lib_Font, "library destroy requested"); + LOG_DEBUG(Lib_Font, + "library destroy params:\n" + " library_handle_ptr={}\n" + " library_handle={}\n", + static_cast(pLibrary), static_cast(lib)); + auto* native = static_cast(lib); + if (native->magic != 0x0F01) { + return ORBIS_FONT_ERROR_INVALID_LIBRARY; + } + + RemoveLibState(lib); + + const auto free_fn = native->alloc_vtbl + ? reinterpret_cast(native->alloc_vtbl[1]) + : nullptr; + if (free_fn) { + free_fn(native->alloc_ctx, native); + } else { + std::free(native); + } + *pLibrary = nullptr; return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontCreateRenderer() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceFontCreateRenderer(const OrbisFontMem* memory, + OrbisFontRendererCreateParams create_params, + OrbisFontRenderer* pRenderer) { + return sceFontCreateRendererWithEdition(memory, create_params, 0, pRenderer); } -s32 PS4_SYSV_ABI sceFontCreateRendererWithEdition() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceFontCreateRendererWithEdition(const OrbisFontMem* memory, + OrbisFontRendererCreateParams create_params, + u64 edition, OrbisFontRenderer* pRenderer) { + LOG_INFO(Lib_Font, "called"); + + LOG_DEBUG(Lib_Font, "{}", + formatParams({ + Param("memory", memory), + Param("create_params", create_params), + Param("edition", edition), + Param("pRenderer", pRenderer), + })); + + s32 rc = ORBIS_FONT_ERROR_INVALID_PARAMETER; + if (!memory) { + if (!pRenderer) { + LOG_ERROR(Lib_Font, "INVALID_PARAMETER"); + return rc; + } + *pRenderer = nullptr; + LOG_ERROR(Lib_Font, "INVALID_PARAMETER"); + return rc; + } + + rc = ORBIS_FONT_ERROR_INVALID_MEMORY; + if (memory->mem_kind == 0x0F00 && memory->iface && memory->iface->alloc && + memory->iface->dealloc) { + if (!create_params) { + rc = ORBIS_FONT_ERROR_INVALID_PARAMETER; + } else { + rc = ORBIS_FONT_ERROR_INVALID_PARAMETER; + if (pRenderer) { + const auto alloc_fn = reinterpret_cast(memory->iface->alloc); + const auto free_fn = reinterpret_cast(memory->iface->dealloc); + + const auto* selection = + static_cast( + create_params); + const u32 render_size = selection ? selection->size : 0u; + void* renderer_mem = alloc_fn(memory->mspace_handle, render_size); + void* workspace = alloc_fn(memory->mspace_handle, 0x4000); + + rc = ORBIS_FONT_ERROR_ALLOCATION_FAILED; + if (renderer_mem && workspace) { + auto* renderer = static_cast(renderer_mem); + + std::memset(renderer, 0, offsetof(Internal::RendererOpaque, mem_kind)); + renderer->magic = 0x0F07; + renderer->mem_kind = 0x0F00; + renderer->region_size = memory->region_size; + renderer->region_base = memory->region_base; + renderer->alloc_ctx = memory->mspace_handle; + + renderer->mem_iface = memory->iface; + renderer->alloc_fn = reinterpret_cast(memory->iface->alloc); + renderer->free_fn = reinterpret_cast(memory->iface->dealloc); + renderer->realloc_fn = + reinterpret_cast(memory->iface->realloc_fn); + renderer->calloc_fn = + reinterpret_cast(memory->iface->calloc_fn); + + renderer->selection = + const_cast(static_cast(create_params)); + renderer->outline_magic_0x90 = 0x400000000000ull; + renderer->workspace = workspace; + renderer->workspace_size = 0x4000; + renderer->reserved_a8 = 0; + renderer->outline_policy_flag = 0; + + const uintptr_t create_fn_ptr = + selection ? selection->create_fn : static_cast(0); + auto create_fn = + reinterpret_cast(create_fn_ptr); + if (create_fn_ptr == kFtRendererCreateFnAddr) { + create_fn = &Libraries::FontFt::Internal::FtRendererCreate; + } + if (!create_fn) { + rc = ORBIS_FONT_ERROR_FATAL; + } else { + rc = create_fn(renderer_mem); + if (rc == ORBIS_OK) { + s32 sdk_version = 0; + u32 sdk_version_u32 = 0; + if (Libraries::Kernel::sceKernelGetCompiledSdkVersion(&sdk_version) == + ORBIS_OK) { + sdk_version_u32 = static_cast(sdk_version); + } + const u32 edition_hi = static_cast(edition >> 32); + u32 v = edition_hi; + if (sdk_version_u32 != 0) { + v = sdk_version_u32; + if (edition_hi <= sdk_version_u32) { + v = edition_hi; + } + } + if ((v - 0x930000u) < 0x1bd0000u) { + renderer->feature_byte_0x0c |= 1u; + renderer->outline_policy_flag = 1; + } + + *pRenderer = renderer_mem; + return ORBIS_OK; + } + } + } + + if (workspace) { + free_fn(memory->mspace_handle, workspace); + } + if (renderer_mem) { + free_fn(memory->mspace_handle, renderer_mem); + } + } + } + } + + if (!pRenderer) { + LOG_ERROR(Lib_Font, "FAILED"); + return rc; + } + *pRenderer = nullptr; + if (rc == ORBIS_FONT_ERROR_INVALID_PARAMETER) { + LOG_ERROR(Lib_Font, "INVALID_PARAMETER"); + } else if (rc == ORBIS_FONT_ERROR_INVALID_MEMORY) { + LOG_ERROR(Lib_Font, "INVALID_MEMORY"); + } else if (rc == ORBIS_FONT_ERROR_ALLOCATION_FAILED) { + LOG_ERROR(Lib_Font, "ALLOCATION_FAILED"); + } else if (rc == ORBIS_FONT_ERROR_FATAL) { + LOG_ERROR(Lib_Font, "FATAL"); + } else { + LOG_ERROR(Lib_Font, "FAILED"); + } + return rc; } s32 PS4_SYSV_ABI sceFontCreateString() { @@ -182,8 +795,35 @@ s32 PS4_SYSV_ABI sceFontDefineAttribute() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontDeleteGlyph() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceFontDeleteGlyph(const OrbisFontMem* memory, OrbisFontGlyph* glyph) { + LOG_INFO(Lib_Font, "glyph delete requested"); + LOG_DEBUG(Lib_Font, + "glyph delete params:\n" + " memory={}\n" + " glyph_ptr={}\n", + static_cast(memory), + glyph ? static_cast(*glyph) : static_cast(nullptr)); + + if (!glyph) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + OrbisFontGlyph handle = *glyph; + if (!handle) { + return ORBIS_FONT_ERROR_INVALID_GLYPH; + } + + if (handle->magic != 0x0F03) { + return ORBIS_FONT_ERROR_INVALID_GLYPH; + } + + (void)memory; + if (!Internal::ForgetGeneratedGlyph(handle)) { + return ORBIS_FONT_ERROR_INVALID_GLYPH; + } + + auto* boxed = reinterpret_cast(handle); + delete boxed; + *glyph = nullptr; return ORBIS_OK; } @@ -197,14 +837,61 @@ s32 PS4_SYSV_ABI sceFontDestroyGraphicsService() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontDestroyLibrary() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); - return ORBIS_OK; -} +s32 PS4_SYSV_ABI sceFontDestroyRenderer(OrbisFontRenderer* pRenderer) { + if (!pRenderer) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } -s32 PS4_SYSV_ABI sceFontDestroyRenderer() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); - return ORBIS_OK; + auto* renderer = static_cast(*pRenderer); + s32 rc = ORBIS_FONT_ERROR_INVALID_RENDERER; + if (renderer && renderer->magic == 0x0F07) { + const auto kBusy = static_cast(std::numeric_limits::max()); + + std::uintptr_t selection_value = 0; + for (;;) { + selection_value = reinterpret_cast(renderer->selection); + if (selection_value != kBusy) { + std::atomic_ref ref( + *reinterpret_cast(&renderer->selection)); + std::uintptr_t expected = selection_value; + if (ref.compare_exchange_weak(expected, kBusy, std::memory_order_acq_rel)) { + selection_value = expected; + break; + } + } + Libraries::Kernel::sceKernelUsleep(0x1e); + } + + if (selection_value == 0) { + rc = ORBIS_FONT_ERROR_FATAL; + } else { + const auto* selection = + reinterpret_cast( + selection_value); + const auto destroy_fn_ptr = + selection ? selection->destroy_fn : static_cast(0); + auto destroy_fn = reinterpret_cast(destroy_fn_ptr); + if (destroy_fn_ptr == kFtRendererDestroyFnAddr) { + destroy_fn = &Libraries::FontFt::Internal::FtRendererDestroy; + } + rc = destroy_fn ? destroy_fn(renderer) : ORBIS_FONT_ERROR_FATAL; + } + + renderer->selection = nullptr; + + const auto free_fn = reinterpret_cast(renderer->free_fn); + void* alloc_ctx = renderer->alloc_ctx; + void* workspace = renderer->workspace; + if (workspace) { + free_fn(alloc_ctx, workspace); + } + free_fn(alloc_ctx, renderer); + + *pRenderer = nullptr; + return rc; + } + + return rc; } s32 PS4_SYSV_ABI sceFontDestroyString() { @@ -227,8 +914,102 @@ s32 PS4_SYSV_ABI sceFontDettachDeviceCacheBuffer() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontGenerateCharGlyph() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceFontGenerateCharGlyph(OrbisFontHandle glyph_handle, u32 codepoint, + const OrbisFontGenerateGlyphParams* gen_params, + OrbisFontGlyph* glyph_out) { + LOG_INFO(Lib_Font, "glyph generation requested"); + LOG_DEBUG(Lib_Font, + "glyph generation params:\n" + " handle={}\n" + " codepoint={}\n" + " id=0x{:04X}\n" + " glyph_form={}\n" + " metrics_form={}\n" + " form_options=0x{:X}\n" + " mem={}\n", + static_cast(glyph_handle), codepoint, + gen_params ? static_cast(gen_params->id) : 0u, + gen_params ? static_cast(gen_params->glyph_form) : 0u, + gen_params ? static_cast(gen_params->metrics_form) : 0u, + gen_params ? static_cast(gen_params->form_options) : 0u, + gen_params ? static_cast(gen_params->mem) + : static_cast(nullptr)); + + if (!glyph_out) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + *glyph_out = nullptr; + + if (!glyph_handle) { + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + if (codepoint == 0) { + return ORBIS_FONT_ERROR_NO_SUPPORT_CODE; + } + + auto* st = Internal::TryGetState(glyph_handle); + if (!st) { + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + const u8 glyph_form = gen_params ? gen_params->glyph_form : 0; + const u8 metrics_form = gen_params ? gen_params->metrics_form : 0; + const u16 form_options = gen_params ? gen_params->form_options : 0; + const OrbisFontMem* glyph_memory = gen_params ? gen_params->mem : nullptr; + + if (gen_params && gen_params->id != 0x0FD3) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + if ((form_options & static_cast(~0x11)) != 0) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + if (glyph_form == 0 && metrics_form != 0) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + if ((glyph_form != 0 && metrics_form == 0) || glyph_form > 1 || metrics_form > 4) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + if (glyph_memory && glyph_memory->mem_kind != 0xF00) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + + OrbisFontGlyphMetrics metrics{}; + const s32 metrics_res = sceFontGetCharGlyphMetrics(glyph_handle, codepoint, &metrics); + if (metrics_res != ORBIS_OK) { + return metrics_res; + } + + auto* boxed = new (std::nothrow) Libraries::Font::Internal::GeneratedGlyph(); + if (!boxed) { + return ORBIS_FONT_ERROR_ALLOCATION_FAILED; + } + + boxed->codepoint = codepoint; + boxed->metrics = metrics; + boxed->glyph.magic = 0x0F03; + boxed->glyph.flags = form_options; + boxed->glyph.glyph_form = glyph_form; + boxed->glyph.metrics_form = metrics_form; + boxed->glyph.em_size = ClampToU16(st->scale_h); + boxed->glyph.baseline = ClampToU16(metrics.Horizontal.bearingY); + boxed->glyph.height_px = ClampToU16(metrics.height); + boxed->glyph.origin_x = 0; + boxed->glyph.origin_y = boxed->glyph.baseline; + boxed->glyph.scale_x = st->scale_w; + boxed->glyph.base_scale = st->scale_h; + boxed->glyph.memory = glyph_memory; + boxed->owner_handle = glyph_handle; + boxed->outline = {}; + boxed->outline.outline_flags = boxed->glyph.flags; + boxed->outline.points_ptr = nullptr; + boxed->outline.tags_ptr = nullptr; + boxed->outline.contour_end_idx = nullptr; + boxed->outline.contours_cnt = 0; + boxed->outline.points_cnt = 0; + boxed->outline_initialized = false; + + Internal::TrackGeneratedGlyph(&boxed->glyph); + *glyph_out = &boxed->glyph; return ORBIS_OK; } @@ -242,19 +1023,126 @@ s32 PS4_SYSV_ABI sceFontGetCharGlyphCode() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontGetCharGlyphMetrics() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceFontGetCharGlyphMetrics(OrbisFontHandle fontHandle, u32 code, + OrbisFontGlyphMetrics* metrics) { + LOG_INFO(Lib_Font, "called"); + + LOG_DEBUG(Lib_Font, "{}", + formatParams({ + Param("fontHandle", fontHandle), + Param("code", code), + Param("metrics", metrics), + })); + + const s32 rc = Internal::GetCharGlyphMetrics(fontHandle, code, metrics, false); + if (rc != ORBIS_OK) { + if (rc == ORBIS_FONT_ERROR_INVALID_FONT_HANDLE) { + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + } else if (rc == ORBIS_FONT_ERROR_INVALID_PARAMETER) { + LOG_ERROR(Lib_Font, "INVALID_PARAMETER"); + } else if (rc == ORBIS_FONT_ERROR_NOT_BOUND_RENDERER) { + LOG_ERROR(Lib_Font, "NOT_BOUND_RENDERER"); + } + } + return rc; } -s32 PS4_SYSV_ABI sceFontGetEffectSlant() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceFontGetEffectSlant(OrbisFontHandle fontHandle, float* slantRatio) { + LOG_INFO(Lib_Font, "called"); + LOG_DEBUG(Lib_Font, "{}", + formatParams({ + Param("fontHandle", fontHandle), + Param("slantRatio", slantRatio), + })); + + auto* font = GetNativeFont(fontHandle); + if (!font || font->magic != 0x0F02) { + if (slantRatio) { + *slantRatio = 0.0f; + } + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + u32 prev_lock = 0; + if (!AcquireFontLock(font, prev_lock)) { + if (slantRatio) { + *slantRatio = 0.0f; + } + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + const s32 rc = Internal::StyleStateGetSlantRatio(font->style_frame, slantRatio); + ReleaseFontLock(font, prev_lock); + + if (rc != ORBIS_OK) { + if (slantRatio) { + *slantRatio = 0.0f; + } + LOG_ERROR(Lib_Font, "FAILED"); + } + return rc; } -s32 PS4_SYSV_ABI sceFontGetEffectWeight() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceFontGetEffectWeight(OrbisFontHandle fontHandle, float* weightXScale, + float* weightYScale, u32* mode) { + LOG_INFO(Lib_Font, "called"); + LOG_DEBUG(Lib_Font, "{}", + formatParams({ + Param("fontHandle", fontHandle), + Param("weightXScale", weightXScale), + Param("weightYScale", weightYScale), + Param("mode", mode), + })); + + auto* font = GetNativeFont(fontHandle); + if (!font || font->magic != 0x0F02) { + if (weightXScale) { + *weightXScale = 1.0f; + } + if (weightYScale) { + *weightYScale = 1.0f; + } + if (mode) { + *mode = 0; + } + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + u32 prev_lock = 0; + if (!AcquireFontLock(font, prev_lock)) { + if (weightXScale) { + *weightXScale = 1.0f; + } + if (weightYScale) { + *weightYScale = 1.0f; + } + if (mode) { + *mode = 0; + } + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + const s32 rc = + Internal::StyleStateGetWeightScale(font->style_frame, weightXScale, weightYScale, mode); + ReleaseFontLock(font, prev_lock); + + if (rc != ORBIS_OK) { + if (weightXScale) { + *weightXScale = 1.0f; + } + if (weightYScale) { + *weightYScale = 1.0f; + } + if (mode) { + *mode = 0; + } + LOG_ERROR(Lib_Font, "FAILED"); + } + return rc; } s32 PS4_SYSV_ABI sceFontGetFontGlyphsCount() { @@ -287,18 +1175,155 @@ s32 PS4_SYSV_ABI sceFontGetGlyphExpandBufferState() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontGetHorizontalLayout() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceFontGetHorizontalLayout(OrbisFontHandle fontHandle, + OrbisFontHorizontalLayout* layout) { + LOG_INFO(Lib_Font, "called"); + + if (!fontHandle) { + if (layout) { + *layout = {}; + } + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + auto* font = GetNativeFont(fontHandle); + if (!font || font->magic != 0x0F02) { + if (layout) { + *layout = {}; + } + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + u32 prev_lock = 0; + if (!AcquireFontLock(font, prev_lock)) { + if (layout) { + *layout = {}; + } + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + if (!layout) { + font->lock_word = prev_lock; + LOG_ERROR(Lib_Font, "NULL_POINTER"); + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + + LOG_DEBUG(Lib_Font, "{}", + formatParams({ + Param("fontHandle", fontHandle), + Param("layout", layout), + })); + + Internal::HorizontalLayoutBlocks layout_blocks{}; + const s32 rc = + Internal::ComputeHorizontalLayoutBlocksTyped(fontHandle, font->style_frame, &layout_blocks); + font->lock_word = prev_lock; + + if (rc == ORBIS_OK) { + layout->baselineOffset = layout_blocks.baseline(); + layout->lineAdvance = layout_blocks.line_advance(); + layout->decorationExtent = layout_blocks.effect_height(); + LOG_DEBUG(Lib_Font, "GetHorizontalLayout: out baseLineY={} lineHeight={} effectHeight={}", + layout->baselineOffset, layout->lineAdvance, layout->decorationExtent); + return ORBIS_OK; + } + + *layout = {}; + if (const auto* rec = + static_cast(font->open_info.fontset_record)) { + LOG_DEBUG(Lib_Font, + "GetHorizontalLayout: fail detail rc={} fontset_record={} magic=0x{:08X} " + "entry_count={} ctx_entry_index={} sub_font_index={}", + rc, static_cast(font->open_info.fontset_record), rec->magic, + rec->entry_count, font->open_info.ctx_entry_index, + font->open_info.sub_font_index); + const u32 entry_count = rec->entry_count; + const auto* entries = reinterpret_cast(rec + 1); + for (u32 i = 0; i < entry_count; ++i) { + LOG_DEBUG(Lib_Font, "GetHorizontalLayout: fail detail fontset_entry[{}]={}", i, + entries[i]); + } + } else { + LOG_DEBUG(Lib_Font, + "GetHorizontalLayout: fail detail rc={} fontset_record=null ctx_entry_index={} " + "sub_font_index={}", + rc, font->open_info.ctx_entry_index, font->open_info.sub_font_index); + } + LOG_ERROR(Lib_Font, "FAILED"); + return rc; +} + +s32 PS4_SYSV_ABI sceFontGetKerning(OrbisFontHandle fontHandle, u32 preCode, u32 code, + OrbisFontKerning* kerning) { + if (!kerning) { + LOG_DEBUG(Lib_Font, "invalid parameters"); + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + auto& st = Internal::GetState(fontHandle); + if (!st.ext_face_ready || !st.ext_ft_face) { + bool system_attached = false; + const std::string sys_log = + Internal::ReportSystemFaceRequest(st, fontHandle, system_attached); + if (!sys_log.empty()) { + LOG_ERROR(Lib_Font, "{}", sys_log); + } + } + + FT_Face resolved_face = st.ext_ft_face; + float resolved_scale_factor = st.system_font_scale_factor; + FT_UInt g1 = + resolved_face ? FT_Get_Char_Index(resolved_face, static_cast(preCode)) : 0; + FT_UInt g2 = resolved_face ? FT_Get_Char_Index(resolved_face, static_cast(code)) : 0; + if (g1 == 0 || g2 == 0) { + for (const auto& fb : st.system_fallback_faces) { + if (!fb.ready || !fb.ft_face) { + continue; + } + const FT_UInt fb_g1 = FT_Get_Char_Index(fb.ft_face, static_cast(preCode)); + const FT_UInt fb_g2 = FT_Get_Char_Index(fb.ft_face, static_cast(code)); + if (fb_g1 == 0 || fb_g2 == 0) { + continue; + } + resolved_face = fb.ft_face; + resolved_scale_factor = fb.scale_factor; + g1 = fb_g1; + g2 = fb_g2; + break; + } + } + + if (resolved_face && g1 != 0 && g2 != 0) { + const float scaled_w = st.scale_w * resolved_scale_factor; + const float scaled_h = st.scale_h * resolved_scale_factor; + const auto char_w = static_cast(static_cast(scaled_w * 64.0f)); + const auto char_h = static_cast(static_cast(scaled_h * 64.0f)); + (void)FT_Set_Char_Size(resolved_face, char_w, char_h, 72, 72); + + FT_Vector delta{}; + FT_Get_Kerning(resolved_face, g1, g2, FT_KERNING_DEFAULT, &delta); + const float kx = static_cast(delta.x) / 64.0f; + kerning->offsetX = kx; + kerning->offsetY = 0.0f; + kerning->positionX = 0.0f; + kerning->positionY = 0.0f; + return ORBIS_OK; + } + kerning->offsetX = 0.0f; + kerning->offsetY = 0.0f; + kerning->positionX = 0.0f; + kerning->positionY = 0.0f; return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontGetKerning() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceFontGetLibrary() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceFontGetLibrary(OrbisFontHandle fontHandle, OrbisFontLib* pLibrary) { + if (!pLibrary) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + const auto& st = Internal::GetState(fontHandle); + *pLibrary = st.library; return ORBIS_OK; } @@ -307,19 +1332,83 @@ s32 PS4_SYSV_ABI sceFontGetPixelResolution() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontGetRenderCharGlyphMetrics() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceFontGetRenderCharGlyphMetrics(OrbisFontHandle fontHandle, u32 codepoint, + OrbisFontGlyphMetrics* out_metrics) { + LOG_INFO(Lib_Font, "called"); + LOG_DEBUG(Lib_Font, "{}", + formatParams({ + Param("fontHandle", fontHandle), + Param("codepoint", codepoint), + Param("out_metrics", out_metrics), + })); + + const s32 rc = Internal::GetCharGlyphMetrics(fontHandle, codepoint, out_metrics, true); + if (rc != ORBIS_OK) { + if (rc == ORBIS_FONT_ERROR_INVALID_FONT_HANDLE) { + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + } else if (rc == ORBIS_FONT_ERROR_INVALID_PARAMETER) { + LOG_ERROR(Lib_Font, "INVALID_PARAMETER"); + } else if (rc == ORBIS_FONT_ERROR_NOT_BOUND_RENDERER) { + LOG_ERROR(Lib_Font, "NOT_BOUND_RENDERER"); + } + } + return rc; } -s32 PS4_SYSV_ABI sceFontGetRenderEffectSlant() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceFontGetRenderEffectSlant(OrbisFontHandle fontHandle, float* slantRatio) { + s32 rc = ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + auto* font = GetNativeFont(fontHandle); + if (font && font->magic == 0x0F02) { + u32 prev_cached_lock = 0; + if (AcquireCachedStyleLock(font, prev_cached_lock)) { + if (font->renderer_binding.renderer == nullptr) { + rc = ORBIS_FONT_ERROR_NOT_BOUND_RENDERER; + font->cached_style.cache_lock_word = prev_cached_lock; + } else { + rc = Internal::StyleStateGetSlantRatio(&font->cached_style, slantRatio); + font->cached_style.cache_lock_word = prev_cached_lock; + if (rc == ORBIS_OK) { + return ORBIS_OK; + } + } + } + } + if (slantRatio) { + *slantRatio = 0.0f; + } + return rc; } -s32 PS4_SYSV_ABI sceFontGetRenderEffectWeight() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceFontGetRenderEffectWeight(OrbisFontHandle fontHandle, float* weightXScale, + float* weightYScale, u32* mode) { + s32 rc = ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + auto* font = GetNativeFont(fontHandle); + if (font && font->magic == 0x0F02) { + u32 prev_cached_lock = 0; + if (AcquireCachedStyleLock(font, prev_cached_lock)) { + if (font->renderer_binding.renderer == nullptr) { + rc = ORBIS_FONT_ERROR_NOT_BOUND_RENDERER; + font->cached_style.cache_lock_word = prev_cached_lock; + } else { + rc = Internal::StyleStateGetWeightScale(&font->cached_style, weightXScale, + weightYScale, mode); + font->cached_style.cache_lock_word = prev_cached_lock; + if (rc == ORBIS_OK) { + return ORBIS_OK; + } + } + } + } + if (weightXScale) { + *weightXScale = 1.0f; + } + if (weightYScale) { + *weightYScale = 1.0f; + } + if (mode) { + *mode = 0; + } + return rc; } s32 PS4_SYSV_ABI sceFontGetRenderScaledKerning() { @@ -327,14 +1416,58 @@ s32 PS4_SYSV_ABI sceFontGetRenderScaledKerning() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontGetRenderScalePixel() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceFontGetRenderScalePixel(OrbisFontHandle fontHandle, float* out_w, + float* out_h) { + s32 rc = ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + auto* font = GetNativeFont(fontHandle); + if (font) { + u32 prev_cached_lock = 0; + if (AcquireCachedStyleLock(font, prev_cached_lock)) { + if (font->renderer_binding.renderer == nullptr) { + rc = ORBIS_FONT_ERROR_NOT_BOUND_RENDERER; + } else { + rc = Internal::StyleStateGetScalePixel(&font->cached_style, out_w, out_h); + } + ReleaseCachedStyleLock(font, prev_cached_lock); + if (rc == ORBIS_OK) { + return rc; + } + } + } + if (out_w) { + *out_w = 0.0f; + } + if (out_h) { + *out_h = 0.0f; + } + return rc; } -s32 PS4_SYSV_ABI sceFontGetRenderScalePoint() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceFontGetRenderScalePoint(OrbisFontHandle fontHandle, float* out_w, + float* out_h) { + s32 rc = ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + auto* font = GetNativeFont(fontHandle); + if (font) { + u32 prev_cached_lock = 0; + if (AcquireCachedStyleLock(font, prev_cached_lock)) { + if (font->renderer_binding.renderer == nullptr) { + rc = ORBIS_FONT_ERROR_NOT_BOUND_RENDERER; + } else { + rc = Internal::StyleStateGetScalePoint(&font->cached_style, out_w, out_h); + } + ReleaseCachedStyleLock(font, prev_cached_lock); + if (rc == ORBIS_OK) { + return rc; + } + } + } + if (out_w) { + *out_w = 0.0f; + } + if (out_h) { + *out_h = 0.0f; + } + return rc; } s32 PS4_SYSV_ABI sceFontGetResolutionDpi() { @@ -342,14 +1475,77 @@ s32 PS4_SYSV_ABI sceFontGetResolutionDpi() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontGetScalePixel() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceFontGetScalePixel(OrbisFontHandle fontHandle, float* out_w, float* out_h) { + LOG_INFO(Lib_Font, "called"); + + if (!fontHandle) { + if (out_w) { + *out_w = 0.0f; + } + if (out_h) { + *out_h = 0.0f; + } + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + LOG_DEBUG(Lib_Font, "{}", + formatParams({ + Param("fontHandle", fontHandle), + Param("out_w", out_w), + Param("out_h", out_h), + })); + + s32 rc = ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + auto* font = GetNativeFont(fontHandle); + if (font && font->magic == 0x0F02) { + u32 prev_font_lock = 0; + if (AcquireFontLock(font, prev_font_lock)) { + rc = Internal::StyleStateGetScalePixel( + reinterpret_cast(font->style_frame), out_w, out_h); + ReleaseFontLock(font, prev_font_lock); + if (rc == ORBIS_OK) { + return rc; + } + } + } + if (out_w) { + *out_w = 0.0f; + } + if (out_h) { + *out_h = 0.0f; + } + if (rc == ORBIS_FONT_ERROR_INVALID_PARAMETER) { + LOG_ERROR(Lib_Font, "INVALID_PARAMETER"); + } else if (rc == ORBIS_FONT_ERROR_INVALID_FONT_HANDLE) { + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + } else { + LOG_ERROR(Lib_Font, "FAILED"); + } + return rc; } -s32 PS4_SYSV_ABI sceFontGetScalePoint() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceFontGetScalePoint(OrbisFontHandle fontHandle, float* out_w, float* out_h) { + s32 rc = ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + auto* font = GetNativeFont(fontHandle); + if (font) { + u32 prev_font_lock = 0; + if (AcquireFontLock(font, prev_font_lock)) { + rc = Internal::StyleStateGetScalePoint( + reinterpret_cast(font->style_frame), out_w, out_h); + ReleaseFontLock(font, prev_font_lock); + if (rc == ORBIS_OK) { + return rc; + } + } + } + if (out_w) { + *out_w = 0.0f; + } + if (out_h) { + *out_h = 0.0f; + } + return rc; } s32 PS4_SYSV_ABI sceFontGetScriptLanguage() { @@ -362,9 +1558,36 @@ s32 PS4_SYSV_ABI sceFontGetTypographicDesign() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontGetVerticalLayout() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceFontGetVerticalLayout(OrbisFontHandle fontHandle, + OrbisFontVerticalLayout* layout) { + s32 rc = ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + auto* font = GetNativeFont(fontHandle); + if (font && font->magic == 0x0F02) { + u32 prev_font_lock = 0; + if (AcquireFontLock(font, prev_font_lock)) { + if (!layout) { + font->lock_word = prev_font_lock; + rc = ORBIS_FONT_ERROR_INVALID_PARAMETER; + } else { + Internal::VerticalLayoutBlocks layout_words{}; + rc = Internal::ComputeVerticalLayoutBlocksTyped(fontHandle, font->style_frame, + &layout_words); + font->lock_word = prev_font_lock; + if (rc == ORBIS_OK) { + layout->baselineOffsetX = layout_words.baseline_offset_x(); + layout->columnAdvance = layout_words.column_advance(); + layout->decorationSpan = layout_words.decoration_span(); + return ORBIS_OK; + } + } + } + } + + if (layout) { + layout->decorationSpan = 0.0f; + *reinterpret_cast(layout) = 0; + } + return rc; } s32 PS4_SYSV_ABI sceFontGlyphDefineAttribute() { @@ -412,9 +1635,20 @@ s32 PS4_SYSV_ABI sceFontGlyphRefersMetricsHorizontalX() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontGlyphRefersOutline() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); - return ORBIS_OK; +OrbisFontGlyphOutline* PS4_SYSV_ABI sceFontGlyphRefersOutline(OrbisFontGlyph glyph) { + if (!glyph || glyph->magic != 0x0F03 || glyph->glyph_form != 1) { + return nullptr; + } + auto* boxed = Internal::TryGetGeneratedGlyph(glyph); + if (!boxed) { + return nullptr; + } + if (!boxed->outline_initialized) { + if (!Internal::BuildTrueOutline(*boxed)) { + Internal::BuildBoundingOutline(*boxed); + } + } + return &boxed->outline; } s32 PS4_SYSV_ABI sceFontGlyphRenderImage() { @@ -682,170 +1916,3184 @@ s32 PS4_SYSV_ABI sceFontGraphicsUpdateShapeFillPlot() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontMemoryInit() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceFontMemoryInit(OrbisFontMem* mem_desc, void* region_addr, u32 region_size, + const OrbisFontMemInterface* iface, void* mspace_obj, + OrbisFontMemDestroyCb destroy_cb, void* destroy_ctx) { + LOG_INFO(Lib_Font, "called"); + + if (!mem_desc) { + LOG_ERROR(Lib_Font, "NULL_POINTER"); + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + + LOG_DEBUG(Lib_Font, "{}", + formatParams({ + Param("mem_desc", mem_desc), + Param("region_addr", region_addr), + Param("region_size", region_size), + Param("iface", iface), + Param("mspace_obj", mspace_obj), + Param("destroy_cb", reinterpret_cast(destroy_cb)), + Param("destroy_ctx", destroy_ctx), + })); + + if (!iface) { + mem_desc->mem_kind = 0; + if (!region_addr || region_size == 0) { + LOG_ERROR(Lib_Font, "INVALID_PARAMETER"); + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + } + + mem_desc->mem_kind = 0x0F00; + mem_desc->attr_bits = 0; + mem_desc->region_size = region_size; + mem_desc->region_base = region_addr; + mem_desc->mspace_handle = mspace_obj; + mem_desc->iface = iface; + mem_desc->on_destroy = destroy_cb; + mem_desc->destroy_ctx = destroy_ctx; + mem_desc->some_ctx1 = nullptr; + mem_desc->some_ctx2 = mspace_obj; return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontMemoryTerm() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceFontMemoryTerm(OrbisFontMem* mem_desc) { + if (!mem_desc) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + if (mem_desc->mem_kind != 0x0F00) { + return ORBIS_FONT_ERROR_INVALID_MEMORY; + } + + if (static_cast(mem_desc->attr_bits & 0xFF) < 0) { + if (mem_desc->iface && mem_desc->iface->mspace_destroy) { + const auto destroy_fn = + reinterpret_cast( + mem_desc->iface->mspace_destroy); + if (destroy_fn) { + destroy_fn(mem_desc->some_ctx2, mem_desc->mspace_handle); + } + } + std::memset(mem_desc, 0, sizeof(*mem_desc)); + return ORBIS_OK; + } + + if (mem_desc->on_destroy) { + mem_desc->mem_kind = 0; + const auto destroy_fn = reinterpret_cast(mem_desc->on_destroy); + destroy_fn(mem_desc, mem_desc->mspace_handle, mem_desc->destroy_ctx); + return ORBIS_OK; + } + + std::memset(mem_desc, 0, sizeof(*mem_desc)); return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontOpenFontFile() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceFontOpenFontFile(OrbisFontLib library, const char* guest_path, u32 open_mode, + const OrbisFontOpenParams* open_detail, + OrbisFontHandle* out_handle) { + LOG_INFO(Lib_Font, "called"); + + LOG_DEBUG(Lib_Font, "{}", + formatParams({ + Param("library", library), + Param("guest_path", guest_path), + Param("open_mode", open_mode), + Param("open_detail", open_detail), + Param("out_handle", out_handle), + })); + + if (!library) { + if (out_handle) { + *out_handle = nullptr; + } + LOG_ERROR(Lib_Font, "INVALID_LIBRARY"); + return ORBIS_FONT_ERROR_INVALID_LIBRARY; + } + if (!out_handle) { + LOG_ERROR(Lib_Font, "INVALID_PARAMETER"); + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + + auto* lib = static_cast(library); + if (lib->magic != 0x0F01) { + *out_handle = nullptr; + LOG_ERROR(Lib_Font, "INVALID_LIBRARY"); + return ORBIS_FONT_ERROR_INVALID_LIBRARY; + } + + u32 prev_lib_lock = 0; + if (!AcquireLibraryLock(lib, prev_lib_lock)) { + *out_handle = nullptr; + LOG_ERROR(Lib_Font, "INVALID_LIBRARY"); + return ORBIS_FONT_ERROR_INVALID_LIBRARY; + } + + const auto release_library_and_clear_out = [&] { + ReleaseLibraryLock(lib, prev_lib_lock); + *out_handle = nullptr; + }; + + if (!lib->fontset_registry || !lib->sys_driver) { + release_library_and_clear_out(); + LOG_ERROR(Lib_Font, "INVALID_LIBRARY"); + return ORBIS_FONT_ERROR_INVALID_LIBRARY; + } + + const u32 sub_font_index = open_detail ? open_detail->subfont_index : 0u; + const s32 unique_id = open_detail ? open_detail->unique_id : -1; + + const auto alloc_fn = + lib->alloc_vtbl ? reinterpret_cast(lib->alloc_vtbl[0]) : nullptr; + const auto free_fn = + lib->alloc_vtbl ? reinterpret_cast(lib->alloc_vtbl[1]) : nullptr; + if (!alloc_fn || !free_fn) { + release_library_and_clear_out(); + LOG_ERROR(Lib_Font, "INVALID_LIBRARY"); + return ORBIS_FONT_ERROR_INVALID_LIBRARY; + } + + auto* ext_ctx = static_cast(lib->external_fonts_ctx); + if (!ext_ctx) { + release_library_and_clear_out(); + LOG_ERROR(Lib_Font, "NO_SUPPORT_FUNCTION"); + return ORBIS_FONT_ERROR_NO_SUPPORT_FUNCTION; + } + + if (!guest_path || unique_id < -1) { + release_library_and_clear_out(); + LOG_ERROR(Lib_Font, "INVALID_PARAMETER"); + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + + const auto host_path = ResolveGuestPath(guest_path); + const std::filesystem::path path_to_open = + host_path.empty() ? std::filesystem::path(guest_path) : host_path; + const std::string host_path_str = path_to_open.string(); + if (host_path_str.empty()) { + release_library_and_clear_out(); + LOG_ERROR(Lib_Font, "INVALID_PARAMETER"); + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + const std::string guest_name = std::filesystem::path(guest_path).filename().string(); + + OrbisFontHandle handle = *out_handle; + if (handle) { + if (auto* h = GetNativeFont(handle)) { + h->flags = 0; + } + Internal::RemoveState(handle); + } else { + handle = static_cast(alloc_fn(lib->alloc_ctx, 0x100)); + if (!handle) { + release_library_and_clear_out(); + LOG_ERROR(Lib_Font, "ALLOCATION_FAILED"); + return ORBIS_FONT_ERROR_ALLOCATION_FAILED; + } + std::memset(handle, 0, 0x100); + if (auto* h = GetNativeFont(handle)) { + h->flags = 0x10; + } + } + + auto cleanup_handle_for_error = [&](s32 err) { + auto* h = GetNativeFont(handle); + if (!h) { + ReleaseLibraryLock(lib, prev_lib_lock); + *out_handle = nullptr; + return err; + } + h->magic = 0; + h->open_info.unique_id_packed = 0; + h->open_info.ctx_entry_index = 0; + h->open_info.sub_font_index = 0; + h->open_info.fontset_flags = 0; + h->open_info.fontset_record = nullptr; + h->open_info.reserved_0x18 = 0; + h->library = library; + const u16 prev_flags = h->flags; + h->flags = 0; + if ((prev_flags & 0x10) != 0) { + free_fn(lib->alloc_ctx, handle); + } + Internal::RemoveState(handle); + ReleaseLibraryLock(lib, prev_lib_lock); + *out_handle = nullptr; + return err; + }; + + auto* ext_header = reinterpret_cast(ext_ctx); + + auto* ctx_lock_word = &ext_header->lock_word; + for (;;) { + const u32 lw = *ctx_lock_word; + if (static_cast(lw) >= 0) { + std::atomic_ref ref(*ctx_lock_word); + u32 expected = lw; + if (ref.compare_exchange_weak(expected, lw | 0x80000000u, std::memory_order_acq_rel)) { + break; + } + } + Libraries::Kernel::sceKernelUsleep(0x1e); + } + + const u32 font_max = ext_header->max_entries; + auto* entries_base = static_cast(ext_header->base); + if (font_max == 0 || !entries_base) { + *ctx_lock_word = 0; + const s32 err = cleanup_handle_for_error(ORBIS_FONT_ERROR_FONT_OPEN_MAX); + LogFontOpenError(err); + return err; + } + + u32 entry_index = 0xffffffffu; + u32 first_free = 0xffffffffu; + for (u32 i = 0; i < font_max; i++) { + auto* entry = &entries_base[i]; + const u32 active = entry->active; + const u32 stored_id = entry->unique_id; + if (active != 0 && stored_id == static_cast(unique_id)) { + entry_index = i; + break; + } + if (first_free == 0xffffffffu && active == 0) { + first_free = i; + } + } + + if (entry_index == 0xffffffffu) { + entry_index = first_free; + if (entry_index == 0xffffffffu) { + *ctx_lock_word = 0; + const s32 err = cleanup_handle_for_error(ORBIS_FONT_ERROR_FONT_OPEN_MAX); + LogFontOpenError(err); + return err; + } + + auto* entry = &entries_base[entry_index]; + const u32 stored_id = + (unique_id != -1) ? static_cast(unique_id) : (entry_index ^ 0x80000000u); + entry->reserved_0x00 = 0; + entry->active = 1; + entry->font_address = 0; + entry->unique_id = stored_id; + entry->lock_mode1 = 0; + entry->lock_mode3 = 0; + entry->lock_mode2 = 0; + entry->obj_mode1 = nullptr; + entry->obj_mode3 = nullptr; + entry->obj_mode2 = nullptr; + std::memset(entry->reserved_0x38, 0, sizeof(entry->reserved_0x38)); + } + + *ctx_lock_word = 0; + + const u32 packed_unique_id = (unique_id == -1) ? (entry_index + 0x40000000u) + : (static_cast(unique_id) | 0x80000000u); + + void* font_obj = nullptr; + u32 entry_lock_word = 0; + u8* entry_base = + Internal::AcquireFontCtxEntry(ext_ctx, entry_index, open_mode, &font_obj, &entry_lock_word); + auto* entry = entry_base ? reinterpret_cast(entry_base) : nullptr; + if (!entry_base) { + const s32 err = cleanup_handle_for_error(ORBIS_FONT_ERROR_FONT_OPEN_MAX); + LogFontOpenError(err); + return err; + } + + const u32 mode_low = open_mode & 0xF; + + const auto* driver = + lib->sys_driver ? reinterpret_cast(lib->sys_driver) : nullptr; + const auto open_fn = driver ? driver->open : nullptr; + const auto metric_fn = driver ? driver->metric : nullptr; + const auto scale_fn = driver ? driver->scale : nullptr; + if (!open_fn || !metric_fn || !scale_fn) { + const s32 err = cleanup_handle_for_error(ORBIS_FONT_ERROR_INVALID_LIBRARY); + LogFontOpenError(err); + return err; + } + + s32 rc = ORBIS_OK; + u32 updated_lock = entry_lock_word; + void* used_font_obj = font_obj; + + const auto open_driver_mode_for = [&](u32 m) -> u32 { + if (m == 1) { + return 5; + } + if (m == 2) { + return 6; + } + return 7; + }; + + if ((entry_lock_word & 0x40000000u) == 0) { + auto* tail = lib + 1; + auto* tail_reserved1 = + tail ? reinterpret_cast(tail->reserved1) + : nullptr; + const u32 lib_flags = lib->flags; + if (mode_low == 2) { + if ((lib_flags & 0x100000u) != 0) { + tail_reserved1->sysfont_flags |= 1; + } + if ((lib_flags & 0x10000u) != 0) { + tail_reserved1->sysfont_flags |= 2; + } + } else { + if ((lib_flags & 0x200000u) != 0) { + tail_reserved1->sysfont_flags |= 1; + } + if ((lib_flags & 0x20000u) != 0) { + tail_reserved1->sysfont_flags |= 2; + } + } + + if (mode_low != 1 && mode_low != 2 && mode_low != 3) { + rc = ORBIS_FONT_ERROR_INVALID_PARAMETER; + } else { + used_font_obj = font_obj; + rc = open_fn(library, open_driver_mode_for(mode_low), host_path_str.c_str(), 0, + sub_font_index, packed_unique_id, &used_font_obj); + } + + if (tail_reserved1) { + tail_reserved1->sysfont_flags = 0; + } + + if (rc == ORBIS_OK) { + if (mode_low == 3) { + entry->obj_mode3 = used_font_obj; + } else if (mode_low == 2) { + entry->obj_mode2 = used_font_obj; + } else { + entry->obj_mode1 = used_font_obj; + } + updated_lock = entry_lock_word | 0x40000000u; + } + } else { + rc = ORBIS_FONT_ERROR_FONT_OPEN_MAX; + if ((entry_lock_word & 0x0FFFFFFFu) != 0x0FFFFFFFu) { + for (auto* node = static_cast(font_obj); node != nullptr; + node = node->next) { + if (node->sub_font_index == sub_font_index) { + node->refcount++; + used_font_obj = node; + rc = ORBIS_OK; + break; + } + } + if (rc != ORBIS_OK) { + auto* tail = lib + 1; + auto* tail_reserved1 = + tail ? reinterpret_cast(tail->reserved1) + : nullptr; + const u32 lib_flags = lib->flags; + if (mode_low == 2) { + if ((lib_flags & 0x100000u) != 0) { + tail_reserved1->sysfont_flags |= 1; + } + if ((lib_flags & 0x10000u) != 0) { + tail_reserved1->sysfont_flags |= 2; + } + } else { + if ((lib_flags & 0x200000u) != 0) { + tail_reserved1->sysfont_flags |= 1; + } + if ((lib_flags & 0x20000u) != 0) { + tail_reserved1->sysfont_flags |= 2; + } + } + + used_font_obj = font_obj; + rc = open_fn(library, open_driver_mode_for(mode_low), host_path_str.c_str(), 0, + sub_font_index, packed_unique_id, &used_font_obj); + if (tail_reserved1) { + tail_reserved1->sysfont_flags = 0; + } + + if (rc == ORBIS_OK) { + if (mode_low == 3) { + entry->obj_mode3 = used_font_obj; + } else if (mode_low == 2) { + entry->obj_mode2 = used_font_obj; + } else { + entry->obj_mode1 = used_font_obj; + } + } + } + } + } + + if (rc == ORBIS_OK) { + LOG_WARNING(Lib_Font, "font file served: requested=\"{}\" served=\"{}\"", guest_name, + host_path_str); + const u32 next_lock = updated_lock + 1; + updated_lock = next_lock; + + auto* h = GetNativeFont(handle); + if (!h) { + const s32 err = cleanup_handle_for_error(ORBIS_FONT_ERROR_FATAL); + LogFontOpenError(err); + return err; + } + h->magic = 0x0F02; + h->flags = static_cast(h->flags | static_cast(mode_low)); + h->open_info.unique_id_packed = packed_unique_id; + h->open_info.ctx_entry_index = entry_index; + h->open_info.fontset_flags = 0; + h->open_info.fontset_record = nullptr; + h->open_info.reserved_0x18 = 0; + h->open_info.sub_font_index = sub_font_index; + h->library = library; + std::memset(&h->cached_style, 0, sizeof(h->cached_style)); + std::memset(h->reserved_0xcc, 0, sizeof(h->reserved_0xcc)); + std::memset(h->reserved_0x30, 0, sizeof(h->reserved_0x30)); + std::memset(h->reserved_0xe8, 0, sizeof(h->reserved_0xe8)); + h->prevFont = nullptr; + h->nextFont = nullptr; + + u16 metric_buf[2] = {}; + (void)metric_fn(used_font_obj, 0x0e00, metric_buf); + h->metricA = metric_buf[0]; + (void)metric_fn(used_font_obj, 0xea00, metric_buf); + h->metricB = metric_buf[0]; + h->style_frame[0] = 0x48; + h->style_frame[1] = 0x48; + + float scale = 0.0f; + (void)scale_fn(used_font_obj, metric_buf, &scale); + h->style_tail.scale_unit = 0; + h->style_tail.reserved_0x0c = 0; + h->style_tail.scale_w = scale; + h->style_tail.scale_h = scale; + h->style_tail.effect_weight_x = 0.0f; + h->style_tail.effect_weight_y = 0.0f; + h->style_tail.slant_ratio = 0.0f; + h->style_tail.reserved_0x24 = 0.0f; + + *out_handle = handle; + } + + { + const u32 count = updated_lock & 0x0FFFFFFFu; + if (mode_low == 3) { + if (count == 0) { + entry->obj_mode3 = nullptr; + } + entry->lock_mode3 = updated_lock & 0x7fffffffu; + } else if (mode_low == 1) { + if (count == 0) { + entry->obj_mode1 = nullptr; + } + entry->lock_mode1 = updated_lock & 0x7fffffffu; + } else { + if (count == 0) { + entry->obj_mode2 = nullptr; + } + entry->lock_mode2 = updated_lock & 0x7fffffffu; + } + + if (count == 0 && entry->obj_mode1 == nullptr && entry->obj_mode3 == nullptr && + entry->obj_mode2 == nullptr) { + entry->unique_id = 0; + entry->active = 0; + } + } + + if (rc != ORBIS_OK) { + const s32 err = cleanup_handle_for_error(rc); + LogFontOpenError(err); + return err; + } + + const auto* handle_native = GetNativeFont(handle); + if (handle_native && (handle_native->flags & 0x10) != 0) { + auto* tail = reinterpret_cast(lib + 1); + auto* list_lock = &tail->list_head_ptr; + void* list_ptr = nullptr; + for (;;) { + list_ptr = *list_lock; + if (list_ptr != reinterpret_cast(std::numeric_limits::max())) { + std::atomic_ref ref(*list_lock); + void* expected = list_ptr; + if (ref.compare_exchange_weak( + expected, + reinterpret_cast(std::numeric_limits::max()), + std::memory_order_acq_rel)) { + break; + } + } + Libraries::Kernel::sceKernelUsleep(0x1e); + } + + auto* head_ptr = reinterpret_cast(list_ptr); + if (head_ptr) { + const OrbisFontHandle old_head = *head_ptr; + if (auto* h = GetNativeFont(handle)) { + h->prevFont = nullptr; + h->nextFont = old_head; + } + if (old_head) { + if (auto* old_native = GetNativeFont(old_head)) { + old_native->prevFont = handle; + } + } + *head_ptr = handle; + } + *list_lock = list_ptr; + } + + ReleaseLibraryLock(lib, prev_lib_lock); + + std::vector file_bytes; + if (LoadGuestFileBytes(path_to_open, file_bytes) && + file_bytes.size() <= std::numeric_limits::max()) { + auto& st = Internal::GetState(handle); + Internal::DestroyFreeTypeFace(st.ext_ft_face); + for (auto& fb : st.system_fallback_faces) { + Internal::DestroyFreeTypeFace(fb.ft_face); + } + st = {}; + st.library = library; + st.font_set_type = 0; + st.system_font_path.clear(); + st.ext_face_data = std::move(file_bytes); + + const u32 chosen_index = open_detail ? open_detail->subfont_index : 0u; + st.ext_ft_face = Internal::CreateFreeTypeFaceFromBytes( + st.ext_face_data.data(), st.ext_face_data.size(), chosen_index); + if (st.ext_ft_face) { + st.ext_cache.clear(); + st.scratch.clear(); + st.logged_ext_use = false; + st.ext_scale_for_height = 0.0f; + st.layout_cached = false; + const Internal::FaceMetrics m = Internal::LoadFaceMetrics(st.ext_ft_face); + Internal::PopulateStateMetrics(st, m); + st.ext_face_ready = true; + } else { + st.ext_face_ready = false; + } + } + return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontOpenFontInstance() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceFontOpenFontInstance(OrbisFontHandle fontHandle, OrbisFontHandle setupFont, + OrbisFontHandle* pFontHandle) { + LOG_INFO(Lib_Font, "called"); + + if (!fontHandle) { + if (pFontHandle) { + *pFontHandle = nullptr; + } + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + if (!setupFont && !pFontHandle) { + LOG_ERROR(Lib_Font, "INVALID_PARAMETER"); + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + + LOG_DEBUG(Lib_Font, "{}", + formatParams({ + Param("fontHandle", fontHandle), + Param("setupFont", setupFont), + Param("pFontHandle", pFontHandle), + })); + + auto* src = GetNativeFont(fontHandle); + if (!src || src->magic != 0x0F02) { + if (pFontHandle) { + *pFontHandle = nullptr; + } + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + u32 prev_font_lock = 0; + if (!AcquireFontLock(src, prev_font_lock)) { + if (pFontHandle) { + *pFontHandle = nullptr; + } + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + auto release_src_lock = [&] { ReleaseFontLock(src, prev_font_lock); }; + + auto* lib = static_cast(src->library); + if (!lib || lib->magic != 0x0F01) { + release_src_lock(); + if (pFontHandle) { + *pFontHandle = nullptr; + } + LOG_ERROR(Lib_Font, "INVALID_LIBRARY"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + const auto alloc_fn = + lib->alloc_vtbl ? reinterpret_cast(lib->alloc_vtbl[0]) : nullptr; + const auto free_fn = + lib->alloc_vtbl ? reinterpret_cast(lib->alloc_vtbl[1]) : nullptr; + + const u32 open_mode_low = static_cast(src->flags) & 0x0Fu; + const u32 src_sub_font_index = src->open_info.sub_font_index; + + const bool publishable_sysfont_instance = false; + + const auto* fontset_record = + static_cast(src->open_info.fontset_record); + const u32* entry_indices = nullptr; + u32 entry_count = 0; + u8* ctx = nullptr; + + if (!fontset_record) { + entry_count = 1; + entry_indices = &src->open_info.ctx_entry_index; + ctx = static_cast(lib->external_fonts_ctx); + } else { + entry_count = fontset_record->entry_count; + entry_indices = reinterpret_cast(fontset_record + 1); + ctx = static_cast(lib->sysfonts_ctx); + } + + if (!publishable_sysfont_instance && (!ctx || !entry_indices || entry_count == 0)) { + release_src_lock(); + if (pFontHandle) { + *pFontHandle = nullptr; + } + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + OrbisFontHandle out_handle = setupFont; + bool owned = false; + if (!out_handle) { + if (!alloc_fn || !free_fn) { + release_src_lock(); + if (pFontHandle) { + *pFontHandle = nullptr; + } + LOG_ERROR(Lib_Font, "INVALID_LIBRARY"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + out_handle = static_cast(alloc_fn(lib->alloc_ctx, 0x100)); + if (!out_handle) { + release_src_lock(); + if (pFontHandle) { + *pFontHandle = nullptr; + } + LOG_ERROR(Lib_Font, "ALLOCATION_FAILED"); + return ORBIS_FONT_ERROR_ALLOCATION_FAILED; + } + std::memset(out_handle, 0, 0x100); + if (auto* out_native = GetNativeFont(out_handle)) { + out_native->flags = 0x10; + } + owned = true; + } else { + if (auto* out_native = GetNativeFont(out_handle)) { + out_native->flags = 0; + } + Internal::RemoveState(out_handle); + } + + std::memcpy(out_handle, fontHandle, 0x100); + auto* dst = GetNativeFont(out_handle); + dst->lock_word = 0; + dst->cached_style.cache_lock_word = 0; + dst->flags = static_cast((dst->flags & 0xFF0Fu) | (owned ? 0x10u : 0u)); + + if (!publishable_sysfont_instance) { + auto* ctx_header = reinterpret_cast(ctx); + auto* entries_base = ctx_header ? static_cast(ctx_header->base) : nullptr; + const u32 max_entries = ctx_header ? ctx_header->max_entries : 0u; + if (!entries_base || entry_count > max_entries) { + dst->magic = 0; + if (owned && free_fn) { + free_fn(lib->alloc_ctx, out_handle); + } + release_src_lock(); + if (pFontHandle) { + *pFontHandle = nullptr; + } + LOG_ERROR(Lib_Font, "INVALID_PARAMETER"); + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + + std::vector> increments; + increments.reserve(entry_count); + + auto lock_word_ptr_for_mode = [&](Internal::FontCtxEntry* entry, u32 m) -> u32* { + if (!entry) { + return nullptr; + } + if (m == 3) { + return &entry->lock_mode3; + } + if (m == 1) { + return &entry->lock_mode1; + } + if (m == 2) { + return &entry->lock_mode2; + } + return nullptr; + }; + auto obj_slot_for_mode = [&](Internal::FontCtxEntry* entry, u32 m) -> void** { + if (!entry) { + return nullptr; + } + if (m == 3) { + return &entry->obj_mode3; + } + if (m == 1) { + return &entry->obj_mode1; + } + if (m == 2) { + return &entry->obj_mode2; + } + return nullptr; + }; + + s32 rc = ORBIS_OK; + for (u32 i = 0; i < entry_count; i++) { + const u32 entry_index = entry_indices[i]; + if (static_cast(entry_index) < 0) { + continue; + } + if (entry_index >= max_entries) { + rc = ORBIS_FONT_ERROR_INVALID_PARAMETER; + break; + } + + auto* entry = reinterpret_cast( + entries_base + entry_index * sizeof(Internal::FontCtxEntry)); + auto* lock_word_ptr = lock_word_ptr_for_mode(entry, open_mode_low); + auto** obj_slot = obj_slot_for_mode(entry, open_mode_low); + if (!lock_word_ptr || !obj_slot) { + rc = ORBIS_FONT_ERROR_INVALID_PARAMETER; + break; + } + + u32 prev_lock = 0; + for (;;) { + const u32 lw = *lock_word_ptr; + if (static_cast(lw) >= 0) { + std::atomic_ref ref(*lock_word_ptr); + u32 expected = lw; + if (ref.compare_exchange_weak(expected, lw | 0x80000000u, + std::memory_order_acq_rel)) { + prev_lock = lw; + break; + } + } + Libraries::Kernel::sceKernelUsleep(0x1e); + } + + auto unlock_entry = [&](u32 updated) { *lock_word_ptr = updated & 0x7fffffffu; }; + + if ((prev_lock & 0x40000000u) == 0) { + unlock_entry(prev_lock); + rc = ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + break; + } + if ((prev_lock & 0x0FFFFFFFu) == 0x0FFFFFFFu) { + unlock_entry(prev_lock); + rc = ORBIS_FONT_ERROR_FONT_OPEN_MAX; + break; + } + + auto* node = static_cast(*obj_slot); + for (; node != nullptr; node = node->next) { + if (node->sub_font_index == src_sub_font_index) { + break; + } + } + if (!node) { + unlock_entry(prev_lock); + rc = ORBIS_FONT_ERROR_FATAL; + break; + } + + node->refcount++; + unlock_entry(prev_lock + 1); + increments.push_back({entry_index, node}); + } + + if (rc != ORBIS_OK) { + for (const auto& inc : increments) { + if (inc.first >= max_entries || !inc.second) { + continue; + } + auto* entry = reinterpret_cast( + entries_base + inc.first * sizeof(Internal::FontCtxEntry)); + auto* lock_word_ptr = lock_word_ptr_for_mode(entry, open_mode_low); + if (!lock_word_ptr) { + continue; + } + u32 prev_lock = 0; + for (;;) { + const u32 lw = *lock_word_ptr; + if (static_cast(lw) >= 0) { + std::atomic_ref ref(*lock_word_ptr); + u32 expected = lw; + if (ref.compare_exchange_weak(expected, lw | 0x80000000u, + std::memory_order_acq_rel)) { + prev_lock = lw; + break; + } + } + Libraries::Kernel::sceKernelUsleep(0x1e); + } + if ((prev_lock & 0x0FFFFFFFu) != 0) { + inc.second->refcount--; + *lock_word_ptr = (prev_lock - 1) & 0x7fffffffu; + } else { + *lock_word_ptr = prev_lock & 0x7fffffffu; + } + } + + dst->magic = 0; + dst->open_info.unique_id_packed = 0; + dst->open_info.ctx_entry_index = 0; + dst->open_info.sub_font_index = 0; + dst->open_info.fontset_flags = 0; + dst->open_info.fontset_record = nullptr; + dst->open_info.reserved_0x18 = 0; + dst->library = lib; + const u16 prev_flags = dst->flags; + dst->flags = 0; + if ((prev_flags & 0x10) != 0 && free_fn) { + free_fn(lib->alloc_ctx, out_handle); + } + Internal::RemoveState(out_handle); + + release_src_lock(); + if (pFontHandle) { + *pFontHandle = nullptr; + } + LOG_ERROR(Lib_Font, "FAILED"); + return rc; + } + } + + if (owned) { + auto* tail = reinterpret_cast(lib + 1); + auto* list_lock = tail ? &tail->list_head_ptr : nullptr; + void* list_ptr = nullptr; + for (;;) { + list_ptr = list_lock ? *list_lock : nullptr; + if (list_ptr != reinterpret_cast(std::numeric_limits::max())) { + std::atomic_ref ref(*list_lock); + void* expected = list_ptr; + if (ref.compare_exchange_weak( + expected, + reinterpret_cast(std::numeric_limits::max()), + std::memory_order_acq_rel)) { + break; + } + } + Libraries::Kernel::sceKernelUsleep(0x1e); + } + + auto* head_ptr = reinterpret_cast(list_ptr); + if (head_ptr) { + const OrbisFontHandle old_head = *head_ptr; + if (auto* out_native = GetNativeFont(out_handle)) { + out_native->prevFont = nullptr; + out_native->nextFont = old_head; + } + if (old_head) { + if (auto* old_native = GetNativeFont(old_head)) { + old_native->prevFont = out_handle; + } + } + *head_ptr = out_handle; + } + *list_lock = list_ptr; + } + + release_src_lock(); + + if (auto* src_state = Internal::TryGetState(fontHandle)) { + auto& dst_state = Internal::GetState(out_handle); + dst_state = *src_state; + dst_state.ext_cache.clear(); + dst_state.scratch.clear(); + dst_state.logged_ext_use = false; + + const u32 subfont_index = out_handle && GetNativeFont(out_handle) + ? GetNativeFont(out_handle)->open_info.sub_font_index + : 0u; + + dst_state.ext_scale_for_height = 0.0f; + dst_state.layout_cached = false; + dst_state.ext_face_ready = false; + dst_state.ext_ft_face = nullptr; + + if (!dst_state.ext_face_data.empty()) { + dst_state.ext_ft_face = Internal::CreateFreeTypeFaceFromBytes( + dst_state.ext_face_data.data(), dst_state.ext_face_data.size(), subfont_index); + } + if (dst_state.ext_ft_face) { + const Internal::FaceMetrics m = Internal::LoadFaceMetrics(dst_state.ext_ft_face); + Internal::PopulateStateMetrics(dst_state, m); + dst_state.ext_face_ready = true; + } + + for (auto& fb : dst_state.system_fallback_faces) { + fb.ft_face = nullptr; + fb.ready = false; + if (!fb.bytes || fb.bytes->empty()) { + continue; + } + fb.ft_face = Internal::CreateFreeTypeFaceFromBytes(fb.bytes->data(), fb.bytes->size(), + subfont_index); + fb.ready = (fb.ft_face != nullptr); + } + } else { + auto& dst_state = Internal::GetState(out_handle); + Internal::DestroyFreeTypeFace(dst_state.ext_ft_face); + for (auto& fb : dst_state.system_fallback_faces) { + Internal::DestroyFreeTypeFace(fb.ft_face); + } + dst_state = {}; + dst_state.library = static_cast(src->library); + if (fontset_record) { + dst_state.font_set_type = fontset_record->font_set_type; + dst_state.system_requested = true; + } + } + + if (pFontHandle) { + *pFontHandle = out_handle; + } return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontOpenFontMemory() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceFontOpenFontMemory(OrbisFontLib library, const void* fontAddress, u32 fontSize, + const OrbisFontOpenParams* open_params, + OrbisFontHandle* pFontHandle) { + LOG_INFO(Lib_Font, "called"); + + LOG_DEBUG(Lib_Font, "{}", + formatParams({ + Param("library", library), + Param("fontAddress", fontAddress), + Param("fontSize", fontSize), + Param("open_params", open_params), + Param("pFontHandle", pFontHandle), + })); + + s32 rc = ORBIS_FONT_ERROR_INVALID_LIBRARY; + if (!library) { + if (pFontHandle) { + *pFontHandle = nullptr; + } + LOG_ERROR(Lib_Font, "INVALID_LIBRARY"); + return rc; + } + + auto* lib = static_cast(library); + if (lib->magic != 0x0F01) { + if (pFontHandle) { + *pFontHandle = nullptr; + } + LOG_ERROR(Lib_Font, "INVALID_LIBRARY"); + return rc; + } + + u32 prev_lib_lock = 0; + if (!AcquireLibraryLock(lib, prev_lib_lock)) { + if (pFontHandle) { + *pFontHandle = nullptr; + } + LOG_ERROR(Lib_Font, "INVALID_LIBRARY"); + return ORBIS_FONT_ERROR_INVALID_LIBRARY; + } + + auto release_library_and_clear_out = [&] { + ReleaseLibraryLock(lib, prev_lib_lock); + if (pFontHandle) { + *pFontHandle = nullptr; + } + }; + + if (!lib->fontset_registry || !lib->sys_driver) { + release_library_and_clear_out(); + LOG_ERROR(Lib_Font, "INVALID_LIBRARY"); + return ORBIS_FONT_ERROR_INVALID_LIBRARY; + } + + const auto alloc_fn = + lib->alloc_vtbl ? reinterpret_cast(lib->alloc_vtbl[0]) : nullptr; + const auto free_fn = + lib->alloc_vtbl ? reinterpret_cast(lib->alloc_vtbl[1]) : nullptr; + if (!alloc_fn || !free_fn) { + release_library_and_clear_out(); + LOG_ERROR(Lib_Font, "INVALID_LIBRARY"); + return ORBIS_FONT_ERROR_INVALID_LIBRARY; + } + + auto* ext_ctx = static_cast(lib->external_fonts_ctx); + if (!ext_ctx) { + release_library_and_clear_out(); + LOG_ERROR(Lib_Font, "NO_SUPPORT_FUNCTION"); + return ORBIS_FONT_ERROR_NO_SUPPORT_FUNCTION; + } + + const u32 sub_font_index = open_params ? open_params->subfont_index : 0u; + const s32 unique_id = open_params ? open_params->unique_id : -1; + if (!fontAddress || fontSize == 0 || unique_id < -1 || !pFontHandle) { + release_library_and_clear_out(); + LOG_ERROR(Lib_Font, "INVALID_PARAMETER"); + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + + LOG_WARNING(Lib_Font, "font memory requested: addr={} size={} subfont_index={} unique_id={}", + static_cast(fontAddress), fontSize, sub_font_index, unique_id); + + OrbisFontHandle handle = *pFontHandle; + if (handle) { + if (auto* h = GetNativeFont(handle)) { + h->flags = 0; + } + Internal::RemoveState(handle); + } else { + handle = static_cast(alloc_fn(lib->alloc_ctx, 0x100)); + if (!handle) { + release_library_and_clear_out(); + LOG_ERROR(Lib_Font, "ALLOCATION_FAILED"); + return ORBIS_FONT_ERROR_ALLOCATION_FAILED; + } + std::memset(handle, 0, 0x100); + if (auto* h = GetNativeFont(handle)) { + h->flags = 0x10; + } + } + + auto cleanup_handle_for_error = [&](s32 err) { + auto* h = GetNativeFont(handle); + if (!h) { + ReleaseLibraryLock(lib, prev_lib_lock); + *pFontHandle = nullptr; + return err; + } + h->magic = 0; + h->open_info.unique_id_packed = 0; + h->open_info.ctx_entry_index = 0; + h->open_info.sub_font_index = 0; + h->open_info.fontset_flags = 0; + h->open_info.fontset_record = nullptr; + h->open_info.reserved_0x18 = 0; + h->library = library; + const u16 prev_flags = h->flags; + h->flags = 0; + if ((prev_flags & 0x10) != 0) { + free_fn(lib->alloc_ctx, handle); + } + Internal::RemoveState(handle); + ReleaseLibraryLock(lib, prev_lib_lock); + *pFontHandle = nullptr; + return err; + }; + + auto* ext_header = reinterpret_cast(ext_ctx); + + auto* ctx_lock_word = &ext_header->lock_word; + for (;;) { + const u32 lw = *ctx_lock_word; + if (static_cast(lw) >= 0) { + std::atomic_ref ref(*ctx_lock_word); + u32 expected = lw; + if (ref.compare_exchange_weak(expected, lw | 0x80000000u, std::memory_order_acq_rel)) { + break; + } + } + Libraries::Kernel::sceKernelUsleep(0x1e); + } + + const u32 font_max = ext_header->max_entries; + auto* entries_base = static_cast(ext_header->base); + + u32 entry_index = 0xffffffffu; + if (font_max != 0 && entries_base) { + const u32 unique_u32 = static_cast(unique_id); + u32 first_free = 0xffffffffu; + for (u32 i = 0; i < font_max; i++) { + const auto* entry = &entries_base[i]; + const u32 active = entry->active; + const u32 stored_id = entry->unique_id; + const u64 stored_addr = entry->font_address; + + if (((active != 0) && (stored_id == unique_u32)) || + stored_addr == reinterpret_cast(fontAddress)) { + entry_index = i; + break; + } + if (first_free == 0xffffffffu && active == 0) { + first_free = i; + } + } + + if (entry_index == 0xffffffffu) { + entry_index = first_free; + if (entry_index != 0xffffffffu) { + auto* entry = &entries_base[entry_index]; + const u32 stored_id = + (unique_u32 != 0xffffffffu) ? unique_u32 : (entry_index ^ 0x80000000u); + entry->reserved_0x00 = 0; + entry->active = 1; + entry->font_address = reinterpret_cast(fontAddress); + entry->unique_id = stored_id; + entry->lock_mode1 = 0; + entry->lock_mode3 = 0; + entry->lock_mode2 = 0; + entry->obj_mode1 = nullptr; + entry->obj_mode3 = nullptr; + entry->obj_mode2 = nullptr; + std::memset(entry->reserved_0x38, 0, sizeof(entry->reserved_0x38)); + } + } + } + + *ctx_lock_word = 0; + + if (entry_index == 0xffffffffu) { + const s32 err = cleanup_handle_for_error(ORBIS_FONT_ERROR_FONT_OPEN_MAX); + LogFontOpenError(err); + return err; + } + + const u32 uvar15 = (unique_id == -1) ? (entry_index + 0x40000000u) + : (static_cast(unique_id) | 0x80000000u); + + auto* entry = &entries_base[entry_index]; + auto* entry_lock_word = &entry->lock_mode1; + u32 entry_prev = 0; + u32 entry_locked = 0; + for (;;) { + entry_prev = *entry_lock_word; + if (static_cast(entry_prev) >= 0) { + std::atomic_ref ref(*entry_lock_word); + u32 expected = entry_prev; + const u32 desired = entry_prev | 0x80000000u; + if (ref.compare_exchange_weak(expected, desired, std::memory_order_acq_rel)) { + entry_locked = desired; + break; + } + } + Libraries::Kernel::sceKernelUsleep(0x1e); + } + + auto release_entry_lock = [&] { + if ((entry_locked & 0x0FFFFFFFu) == 0) { + entry->obj_mode1 = nullptr; + if (entry->obj_mode3 == nullptr && entry->obj_mode2 == nullptr) { + entry->unique_id = 0; + entry->active = 0; + } + } + *entry_lock_word = entry_locked & 0x7fffffffu; + }; + + void* font_obj = entry->obj_mode1; + + const auto* driver = + lib->sys_driver ? reinterpret_cast(lib->sys_driver) : nullptr; + const auto open_fn = driver ? driver->open : nullptr; + const auto metric_fn = driver ? driver->metric : nullptr; + const auto scale_fn = driver ? driver->scale : nullptr; + if (!open_fn || !metric_fn || !scale_fn) { + release_entry_lock(); + const s32 err = cleanup_handle_for_error(ORBIS_FONT_ERROR_INVALID_LIBRARY); + LogFontOpenError(err); + return err; + } + + rc = ORBIS_OK; + bool need_open = (entry_prev & 0x40000000u) == 0; + + if (!need_open) { + rc = ORBIS_FONT_ERROR_FONT_OPEN_MAX; + if ((entry_prev & 0x0FFFFFFFu) != 0x0FFFFFFFu) { + for (auto* node = static_cast(font_obj); node != nullptr; + node = node->next) { + if (node->sub_font_index == sub_font_index) { + node->refcount++; + font_obj = node; + rc = ORBIS_OK; + break; + } + } + if (rc != ORBIS_OK) { + need_open = true; + } + } + } + + if (need_open) { + auto* tail = lib + 1; + auto* tail_reserved1 = + tail ? reinterpret_cast(tail->reserved1) + : nullptr; + const u32 lib_flags = lib->flags; + if ((lib_flags & 0x200000u) != 0) { + tail_reserved1->sysfont_flags |= 1; + } + if ((lib_flags & 0x20000u) != 0) { + tail_reserved1->sysfont_flags |= 2; + } + + rc = open_fn(library, 1, fontAddress, fontSize, sub_font_index, uvar15, &font_obj); + if (tail_reserved1) { + tail_reserved1->sysfont_flags = 0; + } + + if (rc == ORBIS_OK) { + entry->obj_mode1 = font_obj; + entry_locked = entry_prev | 0xC0000000u; + } + } + + if (rc == ORBIS_OK) { + auto* h = GetNativeFont(handle); + if (!h) { + release_entry_lock(); + const s32 err = cleanup_handle_for_error(ORBIS_FONT_ERROR_FATAL); + LogFontOpenError(err); + return err; + } + h->magic = 0x0F02; + h->flags = static_cast(h->flags | 1); + entry_locked = entry_locked + 1; + h->open_info.unique_id_packed = uvar15; + h->open_info.ctx_entry_index = entry_index; + h->open_info.fontset_flags = 0; + h->open_info.fontset_record = nullptr; + h->open_info.reserved_0x18 = 0; + h->open_info.sub_font_index = sub_font_index; + h->library = library; + std::memset(&h->cached_style, 0, sizeof(h->cached_style)); + std::memset(h->reserved_0xcc, 0, sizeof(h->reserved_0xcc)); + std::memset(h->reserved_0x30, 0, sizeof(h->reserved_0x30)); + std::memset(h->reserved_0xe8, 0, sizeof(h->reserved_0xe8)); + h->prevFont = nullptr; + h->nextFont = nullptr; + + u16 metric_buf[2] = {}; + (void)metric_fn(font_obj, 0x0e00, metric_buf); + h->metricA = metric_buf[0]; + (void)metric_fn(font_obj, 0xea00, metric_buf); + h->metricB = metric_buf[0]; + h->style_frame[0] = 0x48; + h->style_frame[1] = 0x48; + + float scale = 0.0f; + (void)scale_fn(font_obj, metric_buf, &scale); + h->style_tail.scale_unit = 0; + h->style_tail.reserved_0x0c = 0; + h->style_tail.scale_w = scale; + h->style_tail.scale_h = scale; + h->style_tail.effect_weight_x = 0.0f; + h->style_tail.effect_weight_y = 0.0f; + h->style_tail.slant_ratio = 0.0f; + h->style_tail.reserved_0x24 = 0.0f; + + *pFontHandle = handle; + } + + release_entry_lock(); + + if (rc != ORBIS_OK) { + const s32 err = cleanup_handle_for_error(rc); + LogFontOpenError(err); + return err; + } + + const auto* handle_native = GetNativeFont(handle); + if (handle_native && (handle_native->flags & 0x10) != 0) { + auto* tail = reinterpret_cast(lib + 1); + auto* list_lock = &tail->list_head_ptr; + void* list_ptr = nullptr; + for (;;) { + list_ptr = *list_lock; + if (list_ptr != reinterpret_cast(std::numeric_limits::max())) { + std::atomic_ref ref(*list_lock); + void* expected = list_ptr; + if (ref.compare_exchange_weak( + expected, + reinterpret_cast(std::numeric_limits::max()), + std::memory_order_acq_rel)) { + break; + } + } + Libraries::Kernel::sceKernelUsleep(0x1e); + } + + auto* head_ptr = reinterpret_cast(list_ptr); + if (head_ptr) { + const OrbisFontHandle old_head = *head_ptr; + if (auto* h = GetNativeFont(handle)) { + h->prevFont = nullptr; + h->nextFont = old_head; + } + if (old_head) { + if (auto* old_native = GetNativeFont(old_head)) { + old_native->prevFont = handle; + } + } + *head_ptr = handle; + } + *list_lock = list_ptr; + } + + ReleaseLibraryLock(lib, prev_lib_lock); + + auto& st = Internal::GetState(handle); + Internal::DestroyFreeTypeFace(st.ext_ft_face); + for (auto& fb : st.system_fallback_faces) { + Internal::DestroyFreeTypeFace(fb.ft_face); + } + st = {}; + st.library = library; + st.font_set_type = 0; + st.system_font_path.clear(); + { + const u8* src = static_cast(fontAddress); + st.ext_face_data.assign(src, src + fontSize); + } + + const u32 chosen_index = open_params ? open_params->subfont_index : 0u; + st.ext_ft_face = Internal::CreateFreeTypeFaceFromBytes(st.ext_face_data.data(), + st.ext_face_data.size(), chosen_index); + + if (st.ext_ft_face) { + st.ext_cache.clear(); + st.scratch.clear(); + st.logged_ext_use = false; + st.ext_scale_for_height = 0.0f; + st.layout_cached = false; + const Internal::FaceMetrics m = Internal::LoadFaceMetrics(st.ext_ft_face); + Internal::PopulateStateMetrics(st, m); + st.ext_face_ready = true; + } else { + st.ext_face_ready = false; + } + return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontOpenFontSet() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceFontOpenFontSet(OrbisFontLib library, u32 fontSetType, u32 openMode, + const OrbisFontOpenParams* open_params, + OrbisFontHandle* pFontHandle) { + LOG_INFO(Lib_Font, "called"); + LOG_DEBUG(Lib_Font, "{}", + formatParams({ + Param("library", library), + Param("fontSetType", fontSetType), + Param("openMode", openMode), + Param("open_params", open_params), + Param("pFontHandle", pFontHandle), + })); + + { + auto* lib_local = static_cast(library); + if (!lib_local || lib_local->magic != 0x0F01) { + if (pFontHandle) { + *pFontHandle = nullptr; + } + LOG_ERROR(Lib_Font, "INVALID_LIBRARY"); + return ORBIS_FONT_ERROR_INVALID_LIBRARY; + } + if (!pFontHandle) { + LOG_ERROR(Lib_Font, "INVALID_PARAMETER"); + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + + u32 prev_lib_lock = 0; + if (!AcquireLibraryLock(lib_local, prev_lib_lock)) { + *pFontHandle = nullptr; + LOG_ERROR(Lib_Font, "INVALID_LIBRARY"); + return ORBIS_FONT_ERROR_INVALID_LIBRARY; + } + const auto release_library_and_clear_out = [&](s32 err) -> s32 { + ReleaseLibraryLock(lib_local, prev_lib_lock); + *pFontHandle = nullptr; + return err; + }; + + if (openMode != 1 && openMode != 2 && openMode != 3) { + LOG_ERROR(Lib_Font, "INVALID_PARAMETER"); + return release_library_and_clear_out(ORBIS_FONT_ERROR_INVALID_PARAMETER); + } + const u32 mode_low = openMode & 0x0Fu; + if (!lib_local->fontset_registry || !lib_local->sys_driver) { + LOG_ERROR(Lib_Font, "INVALID_LIBRARY"); + return release_library_and_clear_out(ORBIS_FONT_ERROR_INVALID_LIBRARY); + } + + const auto alloc_fn = + lib_local->alloc_vtbl + ? reinterpret_cast(lib_local->alloc_vtbl[0]) + : nullptr; + const auto free_fn = lib_local->alloc_vtbl + ? reinterpret_cast(lib_local->alloc_vtbl[1]) + : nullptr; + if (!alloc_fn || !free_fn) { + LOG_ERROR(Lib_Font, "INVALID_MEMORY"); + return release_library_and_clear_out(ORBIS_FONT_ERROR_INVALID_MEMORY); + } + + // System font sets always use sub-font-index 0. + const u32 sub_font_index = 0u; + + if (!lib_local->sysfonts_ctx) { + LOG_ERROR(Lib_Font, "NO_SUPPORT_FUNCTION"); + return release_library_and_clear_out(ORBIS_FONT_ERROR_NO_SUPPORT_FUNCTION); + } + + std::filesystem::path primary_path = Internal::ResolveSystemFontPath(fontSetType); + if (primary_path.empty()) { + LOG_ERROR(Lib_Font, "NO_SUPPORT_FONTSET"); + return release_library_and_clear_out(ORBIS_FONT_ERROR_NO_SUPPORT_FONTSET); + } + LOG_INFO(Lib_Font, "OpenFontSet: stage=resolved_primary_path"); + + std::vector primary_bytes; + if (!Internal::LoadFontFile(primary_path, primary_bytes)) { + LOG_ERROR(Lib_Font, "FONT_OPEN_FAILED path='{}' fontsPath='{}'", primary_path.string(), + Config::getFontsPath().string()); + return release_library_and_clear_out(ORBIS_FONT_ERROR_FONT_OPEN_FAILED); + } + LOG_INFO(Lib_Font, "OpenFontSet: stage=loaded_primary_bytes"); + + OrbisFontHandle handle = *pFontHandle; + const bool had_existing_handle = (handle != nullptr); + if (handle) { + if (auto* h = GetNativeFont(handle)) { + h->flags = 0; + } + Internal::RemoveState(handle); + } else { + handle = static_cast(alloc_fn(lib_local->alloc_ctx, 0x100)); + if (!handle) { + LOG_ERROR(Lib_Font, "ALLOCATION_FAILED"); + return release_library_and_clear_out(ORBIS_FONT_ERROR_ALLOCATION_FAILED); + } + std::memset(handle, 0, 0x100); + if (auto* h = GetNativeFont(handle)) { + h->flags = 0x10; + } + } + LOG_INFO(Lib_Font, "OpenFontSet: stage=handle_ready"); + + auto cleanup_handle_for_error = [&](s32 rc) -> s32 { + auto* h = GetNativeFont(handle); + if (!h) { + return release_library_and_clear_out(rc); + } + const u16 prev_flags = h->flags; + h->magic = 0; + h->open_info.unique_id_packed = 0; + h->open_info.ctx_entry_index = 0; + h->open_info.sub_font_index = 0; + h->open_info.fontset_flags = 0; + h->open_info.fontset_record = nullptr; + h->open_info.reserved_0x18 = 0; + h->library = library; + h->flags = 0; + if ((prev_flags & 0x10) != 0) { + free_fn(lib_local->alloc_ctx, handle); + } + Internal::RemoveState(handle); + return release_library_and_clear_out(rc); + }; + + auto* h = GetNativeFont(handle); + if (!h) { + return cleanup_handle_for_error(ORBIS_FONT_ERROR_FATAL); + } + + h->magic = 0; + h->flags = static_cast(h->flags & 0x10); + h->open_info.unique_id_packed = 0; + h->open_info.ctx_entry_index = 0; + h->open_info.fontset_flags = 0; + h->open_info.fontset_record = nullptr; + h->open_info.reserved_0x18 = 0; + h->open_info.sub_font_index = sub_font_index; + h->library = library; + std::memset(&h->cached_style, 0, sizeof(h->cached_style)); + std::memset(h->reserved_0xcc, 0, sizeof(h->reserved_0xcc)); + std::memset(h->reserved_0x30, 0, sizeof(h->reserved_0x30)); + std::memset(h->reserved_0xe8, 0, sizeof(h->reserved_0xe8)); + h->prevFont = nullptr; + h->nextFont = nullptr; + h->style_frame[0] = 0x48; + h->style_frame[1] = 0x48; + h->style_tail.scale_unit = 0; + h->style_tail.reserved_0x0c = 0; + { + const auto* driver = + reinterpret_cast(lib_local->sys_driver); + const auto pixel_res_fn = driver ? driver->pixel_resolution : nullptr; + const u32 pixel_res = pixel_res_fn ? pixel_res_fn() : 0; + const float scale = (pixel_res != 0) ? (1000.0f / static_cast(pixel_res)) : 0.0f; + h->style_tail.scale_w = scale; + h->style_tail.scale_h = scale; + } + h->style_tail.effect_weight_x = 0.0f; + h->style_tail.effect_weight_y = 0.0f; + h->style_tail.slant_ratio = 0.0f; + h->style_tail.reserved_0x24 = 0.0f; + + auto& st = Internal::GetState(handle); + Internal::DestroyFreeTypeFace(st.ext_ft_face); + for (auto& fb : st.system_fallback_faces) { + Internal::DestroyFreeTypeFace(fb.ft_face); + } + st = {}; + st.library = library; + st.font_set_type = fontSetType; + st.system_font_path = primary_path; + st.system_requested = true; + st.system_font_id = 0; + st.system_font_scale_factor = 1.0f; + st.system_font_shift_value = 0; + st.ext_face_data = std::move(primary_bytes); + st.ext_ft_face = Internal::CreateFreeTypeFaceFromBytes( + st.ext_face_data.data(), st.ext_face_data.size(), sub_font_index); + if (!st.ext_ft_face) { + LOG_ERROR(Lib_Font, "FONT_OPEN_FAILED"); + return cleanup_handle_for_error(ORBIS_FONT_ERROR_FONT_OPEN_FAILED); + } + LOG_INFO(Lib_Font, "OpenFontSet: stage=created_primary_face"); + const Internal::FaceMetrics m = Internal::LoadFaceMetrics(st.ext_ft_face); + Internal::PopulateStateMetrics(st, m); + st.ext_face_ready = true; + + { + const u16 units = static_cast(st.ext_ft_face->units_per_EM); + h->metricA = units; + u16 ascender = units; + if (const TT_OS2* os2 = + static_cast(FT_Get_Sfnt_Table(st.ext_ft_face, ft_sfnt_os2))) { + ascender = static_cast(os2->sTypoAscender); + } + h->metricB = ascender; + + const auto* driver = + reinterpret_cast(lib_local->sys_driver); + const auto pixel_res_fn = driver ? driver->pixel_resolution : nullptr; + const u32 pixel_res = pixel_res_fn ? pixel_res_fn() : 0; + const float scale = (pixel_res != 0) + ? (static_cast(units) / static_cast(pixel_res)) + : 0.0f; + h->style_tail.scale_w = scale; + h->style_tail.scale_h = scale; + } + + auto compute_sysfont_scale_factor = [](FT_Face face, int units_per_em) -> float { + (void)units_per_em; + if (!face) { + return 1.0f; + } + const TT_OS2* os2 = static_cast(FT_Get_Sfnt_Table(face, ft_sfnt_os2)); + if (os2) { + if (os2->sTypoAscender == 770 && os2->sTypoDescender == -230) { + return 1024.0f / 952.0f; + } + } + return 1.0f; + }; + auto compute_sysfont_shift_value = [](FT_Face face) -> s32 { + if (!face) { + return 0; + } + const TT_OS2* os2 = static_cast(FT_Get_Sfnt_Table(face, ft_sfnt_os2)); + if (!os2) { + return 0; + } + const bool is_jp_pro_metrics = (os2->sTypoAscender == 880) && + (os2->sTypoDescender == -120) && + (os2->sTypoLineGap == 1); + if (is_jp_pro_metrics) { + const auto* vhea = + static_cast(FT_Get_Sfnt_Table(face, ft_sfnt_vhea)); + if (vhea) { + const int units = static_cast(face->units_per_EM); + const int gap = static_cast(os2->sTypoLineGap); + const int shift = 1024 - units - gap; + if (shift > 0 && shift < 128) { + return static_cast(shift); + } + } + } + return 0; + }; + st.system_font_scale_factor = + compute_sysfont_scale_factor(st.ext_ft_face, st.ext_units_per_em); + st.system_font_shift_value = compute_sysfont_shift_value(st.ext_ft_face); + LOG_DEBUG(Lib_Font, "SystemFonts: primary='{}' unitsPerEm={} scaleFactor={} shiftValue={}", + primary_path.filename().string(), st.ext_units_per_em, + st.system_font_scale_factor, st.system_font_shift_value); + + std::string preferred_latin_name_lower; + const auto base_dir = Internal::GetSysFontBaseDir(); + if (!base_dir.empty()) { + auto resolve_existing = + [&](std::string_view filename) -> std::optional { + const std::filesystem::path file_path{std::string(filename)}; + std::error_code ec; + { + const auto candidate = base_dir / file_path; + if (std::filesystem::exists(candidate, ec)) { + return candidate; + } + } + { + const auto candidate = base_dir / "font" / file_path; + if (std::filesystem::exists(candidate, ec)) { + return candidate; + } + } + { + const auto candidate = base_dir / "font2" / file_path; + if (std::filesystem::exists(candidate, ec)) { + return candidate; + } + } + return std::nullopt; + }; + + auto resolve_sysfont_path = [&](const std::filesystem::path& candidate) + -> std::optional { + if (candidate.empty()) { + return std::nullopt; + } + std::error_code ec; + if (std::filesystem::exists(candidate, ec)) { + return candidate; + } + + const auto filename = candidate.filename().string(); + if (filename.empty()) { + return std::nullopt; + } + + if (auto direct = resolve_existing(filename)) { + return direct; + } + if (auto alias = ResolveKnownSysFontAlias(base_dir, filename)) { + return alias; + } + return std::nullopt; + }; + + auto lower_ascii = [](std::string s) { + for (auto& c : s) { + c = static_cast(std::tolower(static_cast(c))); + } + return s; + }; + + const std::string primary_name_lower = lower_ascii(primary_path.filename().string()); + + auto has_fallback_name_lower = [&](const std::string& candidate_lower) -> bool { + if (candidate_lower.empty() || candidate_lower == primary_name_lower) { + return true; + } + for (const auto& fb : st.system_fallback_faces) { + if (lower_ascii(fb.path.filename().string()) == candidate_lower) { + return true; + } + } + return false; + }; + + auto add_fallback_face = [&](const std::filesystem::path& p) { + LOG_DEBUG(Lib_Font, "SystemFonts: add_fallback_face begin"); + std::vector fb_bytes; + if (!Internal::LoadFontFile(p, fb_bytes)) { + LOG_DEBUG(Lib_Font, "SystemFonts: add_fallback_face failed (LoadFontFile)"); + return; + } + LOG_DEBUG(Lib_Font, "SystemFonts: add_fallback_face bytes_size={}", + fb_bytes.size()); + Internal::FontState::SystemFallbackFace fb{}; + fb.font_id = 0xffffffffu; + fb.scale_factor = 1.0f; + fb.shift_value = 0; + fb.path = p; + fb.bytes = std::make_shared>(std::move(fb_bytes)); + fb.ft_face = Internal::CreateFreeTypeFaceFromBytes( + fb.bytes->data(), fb.bytes->size(), sub_font_index); + fb.ready = (fb.ft_face != nullptr); + if (fb.ready) { + LOG_DEBUG(Lib_Font, "SystemFonts: add_fallback_face face={}", + fmt::ptr(fb.ft_face)); + fb.scale_factor = compute_sysfont_scale_factor( + fb.ft_face, fb.ft_face ? static_cast(fb.ft_face->units_per_EM) : 0); + fb.shift_value = compute_sysfont_shift_value(fb.ft_face); + LOG_DEBUG( + Lib_Font, + "SystemFonts: fallback='{}' unitsPerEm={} scaleFactor={} shiftValue={}", + fb.path.filename().string(), + fb.ft_face ? static_cast(fb.ft_face->units_per_EM) : 0, + fb.scale_factor, fb.shift_value); + LOG_DEBUG(Lib_Font, "SystemFonts: add_fallback_face push_back begin"); + st.system_fallback_faces.push_back(std::move(fb)); + LOG_DEBUG(Lib_Font, "SystemFonts: add_fallback_face done"); + } else { + Internal::DestroyFreeTypeFace(fb.ft_face); + } + }; + + { + const u32 tag = (fontSetType >> 8) & 0xFFu; + const u32 variant = (fontSetType >> 20) & 0x0Fu; + u32 style_suffix = fontSetType & 0xFFu; + + switch (style_suffix) { + case 0xC3: + style_suffix = 0x43; + break; + case 0xC4: + style_suffix = 0x44; + break; + case 0xC5: + style_suffix = 0x45; + break; + case 0xC7: + style_suffix = 0x47; + break; + default: + break; + } + + const char* latin_file = nullptr; + if (tag != 0x00u && tag != 0x10u) { + if (variant == 0x3u) { + switch (style_suffix) { + case 0x44: + latin_file = "SSTTypewriter-Roman.otf"; + break; + case 0x47: + latin_file = "SSTTypewriter-Bd.otf"; + break; + default: + break; + } + } else if (variant == 0x1u) { + switch (style_suffix) { + case 0x43: + latin_file = "SST-LightItalic.otf"; + break; + case 0x44: + latin_file = "SST-Italic.otf"; + break; + case 0x45: + latin_file = "SST-MediumItalic.otf"; + break; + case 0x47: + latin_file = "SST-BoldItalic.otf"; + break; + default: + break; + } + } else { + switch (style_suffix) { + case 0x43: + latin_file = "SST-Light.otf"; + break; + case 0x44: + latin_file = "SST-Roman.otf"; + break; + case 0x45: + latin_file = "SST-Medium.otf"; + break; + case 0x47: + latin_file = "SST-Bold.otf"; + break; + default: + break; + } + } + } + + if (latin_file) { + if (auto latin_path = resolve_sysfont_path(base_dir / latin_file)) { + preferred_latin_name_lower = lower_ascii(latin_path->filename().string()); + if (!has_fallback_name_lower(preferred_latin_name_lower)) { + add_fallback_face(*latin_path); + } + } + } + } + + { + const u32 style_suffix = fontSetType & 0xFFu; + const char* arabic_file = nullptr; + switch (style_suffix) { + case 0xC3: + arabic_file = "SSTArabic-Light.otf"; + break; + case 0xC4: + arabic_file = "SSTArabic-Roman.otf"; + break; + case 0xC5: + arabic_file = "SSTArabic-Medium.otf"; + break; + case 0xC7: + arabic_file = "SSTArabic-Bold.otf"; + break; + case 0xD3: + arabic_file = "SSTArabic-Light.otf"; + break; + case 0xD4: + arabic_file = "SSTArabic-Roman.otf"; + break; + case 0xD5: + arabic_file = "SSTArabic-Medium.otf"; + break; + case 0xD7: + arabic_file = "SSTArabic-Bold.otf"; + break; + default: + break; + } + if (arabic_file) { + if (auto arabic_path = resolve_sysfont_path(base_dir / arabic_file)) { + const std::string arabic_lower = + lower_ascii(arabic_path->filename().string()); + if (!has_fallback_name_lower(arabic_lower)) { + add_fallback_face(*arabic_path); + } + } + } + } + + { + const u32 tag = (fontSetType >> 8) & 0xFFu; + const u32 style_suffix = fontSetType & 0xFFu; + + auto add_named_fallback = [&](std::string_view filename) { + if (filename.empty()) { + return; + } + if (auto p = resolve_sysfont_path(base_dir / std::filesystem::path{filename})) { + const std::string lower = lower_ascii(p->filename().string()); + if (!has_fallback_name_lower(lower)) { + add_fallback_face(*p); + } + } + }; + + const bool is_bold_like = (style_suffix == 0x47u) || (style_suffix == 0x57u) || + (style_suffix == 0xC7u) || (style_suffix == 0xD7u); + + const bool needs_jppro = (tag == 0x04u) || (tag == 0x24u) || (tag == 0x34u) || + (tag == 0x84u) || (tag == 0xA4u) || (tag == 0xACu) || + (tag == 0xB4u) || (tag == 0xBCu); + const bool needs_cngb = (tag == 0x80u) || (tag == 0x84u) || (tag == 0x90u) || + (tag == 0xA0u) || (tag == 0xA4u) || (tag == 0xACu) || + (tag == 0xB0u) || (tag == 0xB4u) || (tag == 0xBCu); + const bool needs_hangul = (tag == 0x24u) || (tag == 0x34u) || (tag == 0xA0u) || + (tag == 0xA4u) || (tag == 0xACu) || (tag == 0xB0u) || + (tag == 0xB4u) || (tag == 0xBCu); + + u32 sea_weight_code = style_suffix; + switch (sea_weight_code) { + case 0xD3u: + sea_weight_code = 0x53u; + break; + case 0xD4u: + sea_weight_code = 0x54u; + break; + case 0xD5u: + sea_weight_code = 0x55u; + break; + case 0xD7u: + sea_weight_code = 0x57u; + break; + default: + break; + } + const bool is_sea_weight = (sea_weight_code == 0x53u) || + (sea_weight_code == 0x54u) || + (sea_weight_code == 0x55u) || (sea_weight_code == 0x57u); + + if (is_sea_weight && primary_name_lower.rfind("sstvietnamese-", 0) != 0) { + const char* vn_file = nullptr; + switch (sea_weight_code) { + case 0x53: + vn_file = "SSTVietnamese-Light.otf"; + break; + case 0x54: + vn_file = "SSTVietnamese-Roman.otf"; + break; + case 0x55: + vn_file = "SSTVietnamese-Medium.otf"; + break; + case 0x57: + vn_file = "SSTVietnamese-Bold.otf"; + break; + default: + break; + } + if (vn_file) { + add_named_fallback(vn_file); + } + } + + if (is_sea_weight && + ((tag == 0x10u) || (tag == 0x14u) || (tag == 0x34u) || (tag == 0x90u) || + (tag == 0x94u) || (tag == 0xB0u) || (tag == 0xB4u) || (tag == 0xBCu))) { + const char* th_file = nullptr; + switch (sea_weight_code) { + case 0x53: + th_file = "SSTThai-Light.otf"; + break; + case 0x54: + th_file = "SSTThai-Roman.otf"; + break; + case 0x55: + th_file = "SSTThai-Medium.otf"; + break; + case 0x57: + th_file = "SSTThai-Bold.otf"; + break; + default: + break; + } + if (th_file) { + add_named_fallback(th_file); + } + } + + if (needs_jppro && primary_name_lower.rfind("sstjppro-", 0) != 0) { + add_named_fallback(is_bold_like ? "SSTJpPro-Bold.otf" : "SSTJpPro-Regular.otf"); + } + if (needs_cngb && primary_name_lower != "dfhei5-sony.ttf") { + add_named_fallback("DFHEI5-SONY.ttf"); + } + if (needs_hangul && primary_name_lower.rfind("sceps4yoongd-", 0) != 0) { + add_named_fallback(is_bold_like ? "SCEPS4Yoongd-Bold.otf" + : "SCEPS4Yoongd-Medium.otf"); + } + } + } + + { + const auto* driver = + lib_local->sys_driver + ? reinterpret_cast(lib_local->sys_driver) + : nullptr; + const auto open_fn = driver ? driver->open : nullptr; + if (!open_fn) { + return cleanup_handle_for_error(ORBIS_FONT_ERROR_INVALID_LIBRARY); + } + + auto resolve_served_path = [](const std::filesystem::path& p) -> std::filesystem::path { + if (p.empty()) { + return {}; + } + std::error_code ec; + if (std::filesystem::is_regular_file(p, ec) && !ec) { + return p; + } + const auto parent = p.parent_path(); + const auto parent_name = parent.filename().string(); + const auto file_name = p.filename(); + if (!file_name.empty() && (parent_name == "font" || parent_name == "font2")) { + const auto container = parent.parent_path(); + const auto sibling = + container / ((parent_name == "font") ? "font2" : "font") / file_name; + if (std::filesystem::is_regular_file(sibling, ec) && !ec) { + return sibling; + } + } else { + const auto in_font2_dir = parent / "font2" / file_name; + if (std::filesystem::is_regular_file(in_font2_dir, ec) && !ec) { + return in_font2_dir; + } + const auto in_font_dir = parent / "font" / file_name; + if (std::filesystem::is_regular_file(in_font_dir, ec) && !ec) { + return in_font_dir; + } + } + return p; + }; + + auto stable_unique_id_for = [](std::string_view s) -> s32 { + std::uint32_t h = 2166136261u; + for (unsigned char c : s) { + const unsigned char lower = static_cast(std::tolower(c)); + h ^= static_cast(lower); + h *= 16777619u; + } + h &= 0x7fffffffu; + if (h == 0u) { + h = 1u; + } + return static_cast(h); + }; + + auto open_driver_mode_for = [](u32 m) -> u32 { + if (m == 1) { + return 5; + } + if (m == 2) { + return 6; + } + return 7; + }; + + LOG_INFO(Lib_Font, "OpenFontSet: stage=open_sysfonts_begin"); + auto open_sysfonts_entry = + [&](const std::filesystem::path& requested_path) -> std::optional { + const std::filesystem::path served_path = resolve_served_path(requested_path); + if (served_path.empty()) { + return std::nullopt; + } + const std::string host_path_str = served_path.string(); + if (host_path_str.empty()) { + return std::nullopt; + } + u32 open_arg4 = 0; + { + std::error_code ec; + const auto sz = std::filesystem::file_size(served_path, ec); + if (!ec && sz <= static_cast(std::numeric_limits::max())) { + open_arg4 = static_cast(sz); + } + } + LOG_INFO(Lib_Font, "OpenFontSet: stage=sysfonts_entry_begin"); + const s32 unique_id = stable_unique_id_for(host_path_str); + + auto* sys_ctx = static_cast(lib_local->sysfonts_ctx); + auto* sys_header = + sys_ctx ? reinterpret_cast(sys_ctx) : nullptr; + auto* entries_base = + sys_header ? static_cast(sys_header->base) : nullptr; + const u32 max_entries = sys_header ? sys_header->max_entries : 0u; + if (!sys_header || !entries_base || max_entries == 0) { + return std::nullopt; + } + + u32* ctx_lock_word = &sys_header->lock_word; + for (;;) { + const u32 lw = *ctx_lock_word; + if (static_cast(lw) >= 0) { + std::atomic_ref ref(*ctx_lock_word); + u32 expected = lw; + if (ref.compare_exchange_weak(expected, lw | 0x80000000u, + std::memory_order_acq_rel)) { + break; + } + } + Libraries::Kernel::sceKernelUsleep(0x1e); + } + + u32 entry_index = 0xffffffffu; + u32 first_free = 0xffffffffu; + for (u32 i = 0; i < max_entries; i++) { + auto* entry = &entries_base[i]; + const u32 active = entry->active; + const u32 stored_id = entry->unique_id; + if (active != 0 && stored_id == static_cast(unique_id)) { + entry_index = i; + break; + } + if (first_free == 0xffffffffu && active == 0) { + first_free = i; + } + } + + if (entry_index == 0xffffffffu) { + entry_index = first_free; + if (entry_index == 0xffffffffu) { + *ctx_lock_word = 0; + return std::nullopt; + } + + auto* entry = &entries_base[entry_index]; + entry->reserved_0x00 = 0; + entry->active = 1; + entry->font_address = 0; + entry->unique_id = static_cast(unique_id); + entry->lock_mode1 = 0; + entry->lock_mode3 = 0; + entry->lock_mode2 = 0; + entry->obj_mode1 = nullptr; + entry->obj_mode3 = nullptr; + entry->obj_mode2 = nullptr; + std::memset(entry->reserved_0x38, 0, sizeof(entry->reserved_0x38)); + } + + *ctx_lock_word = 0; + + LOG_INFO(Lib_Font, "OpenFontSet: stage=sysfonts_entry_lock_begin"); + void* font_obj = nullptr; + u32 entry_lock_word = 0; + u8* entry_u8 = Internal::AcquireFontCtxEntry(sys_ctx, entry_index, mode_low, + &font_obj, &entry_lock_word); + auto* entry = + entry_u8 ? reinterpret_cast(entry_u8) : nullptr; + if (!entry) { + return std::nullopt; + } + LOG_INFO(Lib_Font, "OpenFontSet: stage=sysfonts_entry_lock_ok"); + + void* used_font_obj = font_obj; + u32 updated_lock = entry_lock_word; + s32 rc = ORBIS_FONT_ERROR_FONT_OPEN_MAX; + + if ((entry_lock_word & 0x40000000u) == 0) { + used_font_obj = font_obj; + auto* tail = lib_local + 1; + auto* tail_reserved1 = + tail ? reinterpret_cast( + tail->reserved1) + : nullptr; + if (tail_reserved1) { + const u32 lib_flags = lib_local->flags; + if (mode_low == 2) { + if ((lib_flags & 0x100000u) != 0) { + tail_reserved1->sysfont_flags |= 1; + } + if ((lib_flags & 0x10000u) != 0) { + tail_reserved1->sysfont_flags |= 2; + } + } else { + if ((lib_flags & 0x200000u) != 0) { + tail_reserved1->sysfont_flags |= 1; + } + if ((lib_flags & 0x20000u) != 0) { + tail_reserved1->sysfont_flags |= 2; + } + } + } + LOG_INFO(Lib_Font, "OpenFontSet: stage=sysfonts_driver_open_call"); + rc = open_fn(library, open_driver_mode_for(mode_low), host_path_str.c_str(), + open_arg4, sub_font_index, entry_index, &used_font_obj); + if (tail_reserved1) { + tail_reserved1->sysfont_flags = 0; + } + if (rc == ORBIS_OK) { + LOG_INFO(Lib_Font, "OpenFontSet: stage=sysfonts_driver_open_ok"); + if (mode_low == 3) { + entry->obj_mode3 = used_font_obj; + } else if (mode_low == 2) { + entry->obj_mode2 = used_font_obj; + } else { + entry->obj_mode1 = used_font_obj; + } + updated_lock = entry_lock_word | 0x40000000u; + } + } else { + rc = ORBIS_FONT_ERROR_FONT_OPEN_MAX; + if ((entry_lock_word & 0x0FFFFFFFu) != 0x0FFFFFFFu) { + for (auto* node = static_cast(font_obj); + node != nullptr; node = node->next) { + if (node->sub_font_index == sub_font_index) { + node->refcount++; + used_font_obj = node; + rc = ORBIS_OK; + break; + } + } + if (rc != ORBIS_OK) { + used_font_obj = font_obj; + auto* tail = lib_local + 1; + auto* tail_reserved1 = + tail ? reinterpret_cast( + tail->reserved1) + : nullptr; + if (tail_reserved1) { + const u32 lib_flags = lib_local->flags; + if (mode_low == 2) { + if ((lib_flags & 0x100000u) != 0) { + tail_reserved1->sysfont_flags |= 1; + } + if ((lib_flags & 0x10000u) != 0) { + tail_reserved1->sysfont_flags |= 2; + } + } else { + if ((lib_flags & 0x200000u) != 0) { + tail_reserved1->sysfont_flags |= 1; + } + if ((lib_flags & 0x20000u) != 0) { + tail_reserved1->sysfont_flags |= 2; + } + } + } + rc = open_fn(library, open_driver_mode_for(mode_low), + host_path_str.c_str(), open_arg4, sub_font_index, + entry_index, &used_font_obj); + if (tail_reserved1) { + tail_reserved1->sysfont_flags = 0; + } + if (rc == ORBIS_OK) { + if (mode_low == 3) { + entry->obj_mode3 = used_font_obj; + } else if (mode_low == 2) { + entry->obj_mode2 = used_font_obj; + } else { + entry->obj_mode1 = used_font_obj; + } + } + } + } + } + + if (rc == ORBIS_OK) { + updated_lock = updated_lock + 1; + } + + const u32 count = updated_lock & 0x0FFFFFFFu; + if (mode_low == 3) { + if (count == 0) { + entry->obj_mode3 = nullptr; + } + entry->lock_mode3 = updated_lock & 0x7fffffffu; + } else if (mode_low == 1) { + if (count == 0) { + entry->obj_mode1 = nullptr; + } + entry->lock_mode1 = updated_lock & 0x7fffffffu; + } else { + if (count == 0) { + entry->obj_mode2 = nullptr; + } + entry->lock_mode2 = updated_lock & 0x7fffffffu; + } + + if (count == 0 && entry->obj_mode1 == nullptr && entry->obj_mode3 == nullptr && + entry->obj_mode2 == nullptr) { + entry->unique_id = 0; + entry->active = 0; + } + + if (rc != ORBIS_OK) { + return std::nullopt; + } + LOG_INFO(Lib_Font, "OpenFontSet: stage=sysfonts_entry_done"); + return entry_index; + }; + + std::vector font_ids; + font_ids.reserve(1 + st.system_fallback_faces.size()); + + const auto primary_entry = open_sysfonts_entry(primary_path); + if (!primary_entry) { + return cleanup_handle_for_error(ORBIS_FONT_ERROR_FONT_OPEN_FAILED); + } + st.system_font_id = *primary_entry; + font_ids.push_back(st.system_font_id); + LOG_INFO(Lib_Font, "OpenFontSet: stage=sysfonts_primary_opened"); + u32 opened_fallbacks = 0; + for (auto& fb : st.system_fallback_faces) { + if (!fb.ready || !fb.ft_face) { + continue; + } + const auto fb_entry = open_sysfonts_entry(fb.path); + if (!fb_entry) { + continue; + } + fb.font_id = *fb_entry; + font_ids.push_back(fb.font_id); + LOG_INFO(Lib_Font, "OpenFontSet: stage=sysfonts_fallback_opened"); + ++opened_fallbacks; + } + LOG_INFO(Lib_Font, "OpenFontSet: stage=sysfonts_fallbacks_opened"); + + LOG_INFO(Lib_Font, "OpenFontSet: stage=before_make_shared_selector"); + st.fontset_selector = std::make_shared(); + LOG_INFO(Lib_Font, "OpenFontSet: stage=after_make_shared_selector"); + st.fontset_selector->magic = Internal::FontSetSelector::kMagic; + st.fontset_selector->font_set_type = fontSetType; + st.fontset_selector->library = lib_local; + st.fontset_selector->mode_low = mode_low; + st.fontset_selector->sub_font_index = sub_font_index; + st.fontset_selector->primary_font_id = st.system_font_id; + auto lower_ascii_local = [](std::string s) { + for (auto& c : s) { + c = static_cast(std::tolower(static_cast(c))); + } + return s; + }; + const auto select_preferred_latin_id = [&](const std::string& name_lower) -> bool { + if (name_lower.empty()) { + return false; + } + for (const auto& fb : st.system_fallback_faces) { + const auto candidate = lower_ascii_local(fb.path.filename().string()); + if (candidate == name_lower && fb.font_id != 0xffffffffu) { + st.fontset_selector->roman_font_id = fb.font_id; + return true; + } + } + return false; + }; + if (!select_preferred_latin_id(preferred_latin_name_lower)) { + (void)select_preferred_latin_id("sst-roman.otf"); + } + for (const auto& fb : st.system_fallback_faces) { + const auto name = lower_ascii_local(fb.path.filename().string()); + if (name.rfind("sstarabic-", 0) == 0 && fb.font_id != 0xffffffffu) { + st.fontset_selector->arabic_font_id = fb.font_id; + break; + } + } + st.fontset_selector->candidates.clear(); + st.fontset_selector->candidates.reserve(font_ids.size()); + for (u32 id : font_ids) { + st.fontset_selector->candidates.push_back({id, nullptr}); + } + + const u32 entry_count = static_cast(font_ids.size()); + const std::size_t indices_bytes = static_cast(entry_count) * sizeof(u32); + const std::size_t ptr_off_unaligned = + sizeof(Internal::FontSetRecordHeader) + indices_bytes; + const std::size_t ptr_align = alignof(const Internal::FontSetSelector*); + const std::size_t ptr_off = (ptr_off_unaligned + (ptr_align - 1u)) & ~(ptr_align - 1u); + const std::size_t rec_size = ptr_off + sizeof(const Internal::FontSetSelector*); + + LOG_INFO(Lib_Font, "OpenFontSet: stage=before_make_shared_record"); + st.fontset_record_storage = std::make_shared>(rec_size); + LOG_INFO(Lib_Font, "OpenFontSet: stage=after_make_shared_record"); + std::memset(st.fontset_record_storage->data(), 0, st.fontset_record_storage->size()); + + auto* header = std::construct_at(reinterpret_cast( + st.fontset_record_storage->data())); + header->font_set_type = fontSetType; + header->magic = Internal::FontSetSelector::kMagic; + header->entry_count = entry_count; + + auto* entries = reinterpret_cast(st.fontset_record_storage->data() + + sizeof(Internal::FontSetRecordHeader)); + for (u32 i = 0; i < entry_count; ++i) { + std::construct_at(entries + i, font_ids[i]); + } + + std::construct_at(reinterpret_cast( + st.fontset_record_storage->data() + ptr_off), + st.fontset_selector.get()); + + h->open_info.fontset_record = header; + } + + h->magic = 0x0F02; + h->flags = static_cast(h->flags | static_cast(mode_low)); + if (!had_existing_handle) { + *pFontHandle = handle; + } + LOG_INFO(Lib_Font, "OpenFontSet: stage=published_handle"); + if ((h->flags & 0x10) != 0) { + auto* tail = reinterpret_cast(lib_local + 1); + auto* list_lock = &tail->list_head_ptr; + void* list_ptr = nullptr; + for (;;) { + list_ptr = *list_lock; + if (list_ptr != + reinterpret_cast(std::numeric_limits::max())) { + std::atomic_ref ref(*list_lock); + void* expected = list_ptr; + if (ref.compare_exchange_weak( + expected, + reinterpret_cast(std::numeric_limits::max()), + std::memory_order_acq_rel)) { + break; + } + } + Libraries::Kernel::sceKernelUsleep(0x1e); + } + + auto* head_ptr = reinterpret_cast(list_ptr); + if (head_ptr) { + const OrbisFontHandle old_head = *head_ptr; + if (auto* hn = GetNativeFont(handle)) { + hn->prevFont = nullptr; + hn->nextFont = old_head; + } + if (old_head) { + if (auto* old_native = GetNativeFont(old_head)) { + old_native->prevFont = handle; + } + } + *head_ptr = handle; + } + *list_lock = list_ptr; + } + + ReleaseLibraryLock(lib_local, prev_lib_lock); + LOG_INFO(Lib_Font, "OpenFontSet: stage=done"); + return ORBIS_OK; + } +} + +s32 PS4_SYSV_ABI sceFontRebindRenderer(OrbisFontHandle fontHandle) { + LOG_INFO(Lib_Font, "called"); + + if (!fontHandle) { + LOG_ERROR(Lib_Font, "NULL_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + LOG_DEBUG(Lib_Font, "{}", formatParams({Param("fontHandle", fontHandle)})); + + auto* font = GetNativeFont(fontHandle); + if (!font || font->magic != 0x0F02) { + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + u32 prev_font_lock = 0; + if (!AcquireFontLock(font, prev_font_lock)) { + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + u32 prev_cached_lock = 0; + if (!AcquireCachedStyleLock(font, prev_cached_lock)) { + ReleaseFontLock(font, prev_font_lock); + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + s32 rc = ORBIS_FONT_ERROR_FATAL; + void* renderer = font->renderer_binding.renderer; + if (!renderer) { + rc = ORBIS_FONT_ERROR_NOT_BOUND_RENDERER; + } else if (*reinterpret_cast(renderer) != 0x0F07) { + rc = ORBIS_FONT_ERROR_FATAL; + } else { + std::memcpy(&font->cached_style.effectWeightY, &font->style_tail.slant_ratio, sizeof(u64)); + + std::memcpy(&font->cached_style, &font->style_frame[0], sizeof(u32)); + + const u32 dpi_y = font->style_frame[1]; + const u32 scale_unit = font->style_tail.scale_unit; + const u32 reserved_0x0c = font->style_tail.reserved_0x0c; + const float scale_w = font->style_tail.scale_w; + const float scale_h = font->style_tail.scale_h; + const float effect_weight_x = font->style_tail.effect_weight_x; + const float effect_weight_y = font->style_tail.effect_weight_y; + + font->cached_style.hDpi = dpi_y; + font->cached_style.vDpi = scale_unit; + font->cached_style.scaleUnit = reserved_0x0c; + font->cached_style.baseScale = scale_w; + font->cached_style.scalePixelW = scale_h; + font->cached_style.scalePixelH = effect_weight_x; + font->cached_style.effectWeightX = effect_weight_y; + rc = ORBIS_OK; + } + + ReleaseCachedStyleLock(font, prev_cached_lock); + ReleaseFontLock(font, prev_font_lock); + + if (rc != ORBIS_OK) { + if (rc == ORBIS_FONT_ERROR_NOT_BOUND_RENDERER) { + LOG_ERROR(Lib_Font, "NOT_BOUND_RENDERER"); + } else { + LOG_ERROR(Lib_Font, "FAILED"); + } + } + return rc; +} + +s32 PS4_SYSV_ABI sceFontRenderCharGlyphImage(OrbisFontHandle fontHandle, u32 code, + OrbisFontRenderSurface* surf, float x, float y, + OrbisFontGlyphMetrics* metrics, + OrbisFontRenderOutput* result) { + LOG_INFO(Lib_Font, "called"); + + if (!fontHandle) { + ClearRenderOutputs(metrics, result); + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + auto* font = GetNativeFont(fontHandle); + if (!font || font->magic != 0x0F02) { + ClearRenderOutputs(metrics, result); + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + LOG_DEBUG(Lib_Font, "{}", + formatParams({ + Param("fontHandle", fontHandle), + Param("code", code), + Param("surf", surf), + Param("x", x), + Param("y", y), + Param("metrics", metrics), + Param("result", result), + })); + + u32 prev_lock_word = 0; + if (!AcquireFontLock(font, prev_lock_word)) { + ClearRenderOutputs(metrics, result); + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + s32 rc = ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + if (static_cast(font->flags) >= 0) { + s32 pre_rc = ORBIS_OK; + float baseline_add = 0.0f; + + auto cache_cachedstyle_baseline = [&]() -> s32 { + const u8 cached_flags = CachedStyleCacheFlags(font->cached_style); + if ((cached_flags & 0x1) == 0) { + pre_rc = Internal::ComputeHorizontalLayoutBlocksToBytes( + fontHandle, &font->cached_style, font->cached_style.layout_cache_bytes); + if (pre_rc == ORBIS_OK) { + CachedStyleSetCacheFlags(font->cached_style, + static_cast(cached_flags | 0x1)); + pre_rc = ORBIS_OK; + } + } + if (pre_rc == ORBIS_OK) { + const auto layout_words = + Internal::HorizontalLayoutBlocksIo{ + Internal::LayoutWordsBytes(font->cached_style.layout_cache_bytes)} + .fields(); + baseline_add = layout_words.baseline.value(); + } + return pre_rc; + }; + + if (!surf || (surf->styleFlag & 0x1) == 0) { + (void)cache_cachedstyle_baseline(); + } else { + auto* sys = reinterpret_cast(surf->reserved_q); + auto* styleframe = sys ? sys->styleframe : nullptr; + + if (!styleframe || (styleframe->flags1 & 1) == 0) { + (void)cache_cachedstyle_baseline(); + } else { + Internal::StyleStateBlock state{}; + state.dpi_x = styleframe->hDpi; + state.dpi_y = styleframe->vDpi; + state.scale_unit = 0; + state.reserved_0x0c = 0; + state.effect_weight_x = 0.0f; + state.effect_weight_y = 0.0f; + state.slant_ratio = 0.0f; + if ((styleframe->flags1 & 2) != 0) { + state.slant_ratio = styleframe->slantRatio; + } + if ((styleframe->flags1 & 4) != 0) { + state.effect_weight_x = styleframe->effectWeightX; + state.effect_weight_y = styleframe->effectWeightY; + } + + pre_rc = Internal::StyleStateGetScalePixel(&styleframe->hDpi, &state.scale_w, + &state.scale_h); + Internal::HorizontalLayoutBlocks layout_words{}; + if (pre_rc == ORBIS_OK) { + pre_rc = Internal::ComputeHorizontalLayoutBlocksTyped(fontHandle, &state, + &layout_words); + } + if (pre_rc == ORBIS_OK) { + baseline_add = layout_words.baseline(); + if (sys) { + sys->catchedScale = baseline_add; + } + } + } + } + + const float y_used = y + baseline_add; + { + static std::mutex s_baseline_log_mutex; + static std::unordered_map s_baseline_log_counts; + std::lock_guard lock(s_baseline_log_mutex); + int& count = s_baseline_log_counts[fontHandle]; + if (count < 5) { + LOG_DEBUG(Lib_Font, + "RenderBaseline: handle={} code=U+{:04X} y_in={} baseline_add={} " + "y_used={} pre_rc={}", + static_cast(fontHandle), code, y, baseline_add, y_used, + pre_rc); + ++count; + } + } + + CachedStyleSetDirectionWord(font->cached_style, 1); + + if (code == 0) { + rc = ORBIS_FONT_ERROR_NO_SUPPORT_CODE; + } else if (!surf || !metrics || !result) { + rc = ORBIS_FONT_ERROR_INVALID_PARAMETER; + } else if (pre_rc != ORBIS_OK) { + rc = pre_rc; + } else { + rc = Internal::RenderCharGlyphImageCore(fontHandle, code, surf, x, y_used, metrics, + result); + } + } else { + s32 pre_rc = ORBIS_OK; + float scalar_add = 0.0f; + + auto cache_cachedstyle_scalar = [&]() -> s32 { + const u8 cached_flags = CachedStyleCacheFlags(font->cached_style); + if ((cached_flags & 0x2) == 0) { + Internal::VerticalLayoutBlocks layout_words{}; + pre_rc = Internal::ComputeVerticalLayoutBlocksTyped(fontHandle, &font->cached_style, + &layout_words); + if (pre_rc == ORBIS_OK) { + scalar_add = layout_words.baseline_offset_x(); + CachedStyleSetScalar(font->cached_style, scalar_add); + CachedStyleSetCacheFlags(font->cached_style, + static_cast(cached_flags | 0x2)); + } + } + if (pre_rc == ORBIS_OK && (CachedStyleCacheFlags(font->cached_style) & 0x2) != 0) { + scalar_add = CachedStyleGetScalar(font->cached_style); + } + return pre_rc; + }; + + if (!surf || (surf->styleFlag & 0x1) == 0) { + (void)cache_cachedstyle_scalar(); + } else { + auto* sys = reinterpret_cast(surf->reserved_q); + auto* styleframe = sys ? sys->styleframe : nullptr; + if (!styleframe || (styleframe->flags1 & 1) == 0) { + (void)cache_cachedstyle_scalar(); + } else { + Internal::StyleStateBlock state{}; + state.dpi_x = styleframe->hDpi; + state.dpi_y = styleframe->vDpi; + state.scale_unit = 0; + state.reserved_0x0c = 0; + state.effect_weight_x = 0.0f; + state.effect_weight_y = 0.0f; + state.slant_ratio = 0.0f; + if ((styleframe->flags1 & 2) != 0) { + state.slant_ratio = styleframe->slantRatio; + } + if ((styleframe->flags1 & 4) != 0) { + state.effect_weight_x = styleframe->effectWeightX; + state.effect_weight_y = styleframe->effectWeightY; + } + + pre_rc = Internal::StyleStateGetScalePixel(&styleframe->hDpi, &state.scale_w, + &state.scale_h); + Internal::VerticalLayoutBlocks layout_words{}; + if (pre_rc == ORBIS_OK) { + pre_rc = Internal::ComputeVerticalLayoutBlocksTyped(fontHandle, &state, + &layout_words); + } + if (pre_rc == ORBIS_OK) { + scalar_add = layout_words.baseline_offset_x(); + if (sys) { + sys->catchedScale = scalar_add; + } + } else { + (void)cache_cachedstyle_scalar(); + } + } + } + + const float x_used = x + scalar_add; + CachedStyleSetDirectionWord(font->cached_style, 2); + + if (code == 0) { + rc = ORBIS_FONT_ERROR_NO_SUPPORT_CODE; + } else if (!surf || !metrics || !result) { + rc = ORBIS_FONT_ERROR_INVALID_PARAMETER; + } else if (pre_rc != ORBIS_OK) { + rc = pre_rc; + } else { + rc = Internal::RenderCharGlyphImageCore(fontHandle, code, surf, x_used, y, metrics, + result); + } + } + + font->lock_word = prev_lock_word; + + if (rc != ORBIS_OK) { + ClearRenderOutputs(metrics, result); + LOG_ERROR(Lib_Font, "FAILED"); + return rc; + } + LogRenderResultSample(fontHandle, code, *metrics, *result); return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontRebindRenderer() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceFontRenderCharGlyphImageHorizontal(OrbisFontHandle fontHandle, u32 code, + OrbisFontRenderSurface* surf, float x, + float y, OrbisFontGlyphMetrics* metrics, + OrbisFontRenderOutput* result) { + LOG_INFO(Lib_Font, "called"); + + if (!fontHandle) { + ClearRenderOutputs(metrics, result); + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + auto* font = GetNativeFont(fontHandle); + if (!font || font->magic != 0x0F02) { + ClearRenderOutputs(metrics, result); + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + if (code == 0) { + ClearRenderOutputs(metrics, result); + LOG_ERROR(Lib_Font, "NO_SUPPORT_CODE"); + return ORBIS_FONT_ERROR_NO_SUPPORT_CODE; + } + + if (!surf || !metrics || !result) { + ClearRenderOutputs(metrics, result); + LOG_ERROR(Lib_Font, "INVALID_PARAMETER"); + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + + LOG_DEBUG(Lib_Font, "{}", + formatParams({ + Param("fontHandle", fontHandle), + Param("code", code), + Param("surf", surf), + Param("x", x), + Param("y", y), + Param("metrics", metrics), + Param("result", result), + })); + + u32 prev_lock_word = 0; + if (!AcquireFontLock(font, prev_lock_word)) { + ClearRenderOutputs(metrics, result); + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + CachedStyleSetDirectionWord(font->cached_style, 1); + + const s32 rc = + Internal::RenderCharGlyphImageCore(fontHandle, code, surf, x, y, metrics, result); + font->lock_word = prev_lock_word; + + if (rc != ORBIS_OK) { + ClearRenderOutputs(metrics, result); + LOG_ERROR(Lib_Font, "FAILED"); + return rc; + } + LogRenderResultSample(fontHandle, code, *metrics, *result); return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontRenderCharGlyphImage() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceFontRenderCharGlyphImageVertical(OrbisFontHandle fontHandle, u32 code, + OrbisFontRenderSurface* surf, float x, float y, + OrbisFontGlyphMetrics* metrics, + OrbisFontRenderOutput* result) { + LOG_INFO(Lib_Font, "called"); + + if (!fontHandle) { + ClearRenderOutputs(metrics, result); + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + auto* font = GetNativeFont(fontHandle); + if (!font || font->magic != 0x0F02) { + ClearRenderOutputs(metrics, result); + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + if (code == 0) { + ClearRenderOutputs(metrics, result); + LOG_ERROR(Lib_Font, "NO_SUPPORT_CODE"); + return ORBIS_FONT_ERROR_NO_SUPPORT_CODE; + } + + if (!surf || !metrics || !result) { + ClearRenderOutputs(metrics, result); + LOG_ERROR(Lib_Font, "INVALID_PARAMETER"); + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + + LOG_DEBUG(Lib_Font, "{}", + formatParams({ + Param("fontHandle", fontHandle), + Param("code", code), + Param("surf", surf), + Param("x", x), + Param("y", y), + Param("metrics", metrics), + Param("result", result), + })); + + u32 prev_lock_word = 0; + if (!AcquireFontLock(font, prev_lock_word)) { + ClearRenderOutputs(metrics, result); + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + CachedStyleSetDirectionWord(font->cached_style, 2); + + const s32 rc = + Internal::RenderCharGlyphImageCore(fontHandle, code, surf, x, y, metrics, result); + font->lock_word = prev_lock_word; + + if (rc != ORBIS_OK) { + ClearRenderOutputs(metrics, result); + LOG_ERROR(Lib_Font, "FAILED"); + return rc; + } + LogRenderResultSample(fontHandle, code, *metrics, *result); return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontRenderCharGlyphImageHorizontal() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceFontRendererGetOutlineBufferSize(OrbisFontRenderer fontRenderer, u32* size) { + LOG_INFO(Lib_Font, "called"); + + if (!size) { + LOG_ERROR(Lib_Font, "INVALID_PARAMETER"); + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + *size = 0; + + auto* renderer = static_cast(fontRenderer); + if (!renderer || renderer->magic != 0x0F07) { + LOG_ERROR(Lib_Font, "INVALID_RENDERER"); + return ORBIS_FONT_ERROR_INVALID_RENDERER; + } + + LOG_DEBUG(Lib_Font, "{}", + formatParams({ + Param("fontRenderer", fontRenderer), + Param("size", size), + })); + + *size = static_cast(renderer->workspace_size); return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontRenderCharGlyphImageVertical() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceFontRendererResetOutlineBuffer(OrbisFontRenderer fontRenderer) { + LOG_INFO(Lib_Font, "called"); + + auto* renderer = static_cast(fontRenderer); + if (!renderer || renderer->magic != 0x0F07) { + LOG_ERROR(Lib_Font, "INVALID_RENDERER"); + return ORBIS_FONT_ERROR_INVALID_RENDERER; + } + + LOG_DEBUG(Lib_Font, "{}", formatParams({Param("fontRenderer", fontRenderer)})); + + if (renderer->workspace && renderer->workspace_size) { + std::memset(renderer->workspace, 0, static_cast(renderer->workspace_size)); + } + return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontRendererGetOutlineBufferSize() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); - return ORBIS_OK; -} +s32 PS4_SYSV_ABI sceFontRendererSetOutlineBufferPolicy(OrbisFontRenderer fontRenderer, + u64 bufferPolicy, u32 basalSize, + u32 limitSize) { + LOG_INFO(Lib_Font, "called"); -s32 PS4_SYSV_ABI sceFontRendererResetOutlineBuffer() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); - return ORBIS_OK; -} + auto* renderer = static_cast(fontRenderer); + if (!renderer || renderer->magic != 0x0F07) { + LOG_ERROR(Lib_Font, "INVALID_RENDERER"); + return ORBIS_FONT_ERROR_INVALID_RENDERER; + } -s32 PS4_SYSV_ABI sceFontRendererSetOutlineBufferPolicy() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); + LOG_DEBUG(Lib_Font, "{}", + formatParams({ + Param("fontRenderer", fontRenderer), + Param("bufferPolicy", bufferPolicy), + Param("basalSize", basalSize), + Param("limitSize", limitSize), + })); + + if (limitSize != 0 && basalSize > limitSize) { + LOG_ERROR(Lib_Font, "INVALID_PARAMETER"); + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + + const auto alloc_fn = reinterpret_cast(renderer->alloc_fn); + const auto free_fn = reinterpret_cast(renderer->free_fn); + if (!alloc_fn || !free_fn || !renderer->alloc_ctx) { + LOG_ERROR(Lib_Font, "INVALID_MEMORY"); + return ORBIS_FONT_ERROR_INVALID_MEMORY; + } + + std::size_t desired_size = static_cast(renderer->workspace_size); + desired_size = std::max(desired_size, static_cast(basalSize)); + if (limitSize != 0) { + desired_size = std::min(desired_size, static_cast(limitSize)); + } + + if (desired_size == 0) { + desired_size = 0x4000; + } + + if (!renderer->workspace || renderer->workspace_size != desired_size) { + void* new_workspace = alloc_fn(renderer->alloc_ctx, static_cast(desired_size)); + if (!new_workspace) { + LOG_ERROR(Lib_Font, "ALLOCATION_FAILED"); + return ORBIS_FONT_ERROR_ALLOCATION_FAILED; + } + + if (renderer->workspace) { + free_fn(renderer->alloc_ctx, renderer->workspace); + } + + renderer->workspace = new_workspace; + renderer->workspace_size = desired_size; + } + + (void)bufferPolicy; return ORBIS_OK; } void PS4_SYSV_ABI sceFontRenderSurfaceInit(OrbisFontRenderSurface* renderSurface, void* buffer, int bufWidthByte, int pixelSizeByte, int widthPixel, int heightPixel) { - if (renderSurface) { // Ensure surface is not NULL before modifying it - renderSurface->buffer = buffer; - renderSurface->widthByte = bufWidthByte; - renderSurface->pixelSizeByte = pixelSizeByte; + LOG_INFO(Lib_Font, "called"); - // Initialize unknown fields (likely reserved or flags) - renderSurface->unkn_0xd = 0; - renderSurface->styleFlag = 0; - renderSurface->unkn_0xf = 0; - - // Ensure width and height are non-negative - renderSurface->width = (widthPixel < 0) ? 0 : widthPixel; - renderSurface->height = (heightPixel < 0) ? 0 : heightPixel; - - // Set the clipping/scaling rectangle - renderSurface->sc_x0 = 0; - renderSurface->sc_y0 = 0; - renderSurface->sc_x1 = renderSurface->width; - renderSurface->sc_y1 = renderSurface->height; + if (!renderSurface) { + LOG_ERROR(Lib_Font, "NULL_POINTER"); + return; } + + LOG_DEBUG(Lib_Font, "{}", + formatParams({ + Param("renderSurface", renderSurface), + Param("buffer", buffer), + Param("bufWidthByte", bufWidthByte), + Param("pixelSizeByte", pixelSizeByte), + Param("widthPixel", widthPixel), + Param("heightPixel", heightPixel), + })); + + LOG_DEBUG(Lib_Font, "RenderSurfaceInit: writing struct"); + const u32 w_nonneg = (widthPixel < 0) ? 0u : static_cast(widthPixel); + const u32 h_nonneg = (heightPixel < 0) ? 0u : static_cast(heightPixel); + renderSurface->buffer = buffer; + renderSurface->widthByte = bufWidthByte; + renderSurface->pixelSizeByte = static_cast(pixelSizeByte); + renderSurface->pad0 = 0; + renderSurface->styleFlag = 0; + renderSurface->pad2 = 0; + renderSurface->width = static_cast(w_nonneg); + renderSurface->height = static_cast(h_nonneg); + renderSurface->sc_x0 = 0; + renderSurface->sc_y0 = 0; + renderSurface->sc_x1 = w_nonneg; + renderSurface->sc_y1 = h_nonneg; + LOG_DEBUG(Lib_Font, "RenderSurfaceInit: done"); } void PS4_SYSV_ABI sceFontRenderSurfaceSetScissor(OrbisFontRenderSurface* renderSurface, int x0, int y0, int w, int h) { - if (!renderSurface) - return; // Null check + LOG_INFO(Lib_Font, "called"); - // Handle horizontal clipping - int surfaceWidth = renderSurface->width; - int clip_x0, clip_x1; - - if (surfaceWidth != 0) { - if (x0 < 0) { // Adjust for negative x0 - clip_x0 = 0; - clip_x1 = (w + x0 > surfaceWidth) ? surfaceWidth : w + x0; - if (w <= -x0) - clip_x1 = 0; // Entire width is clipped - } else { - clip_x0 = (x0 > surfaceWidth) ? surfaceWidth : x0; - clip_x1 = (w + x0 > surfaceWidth) ? surfaceWidth : w + x0; - } - renderSurface->sc_x0 = clip_x0; - renderSurface->sc_x1 = clip_x1; + if (!renderSurface) { + LOG_ERROR(Lib_Font, "NULL_POINTER"); + return; } - // Handle vertical clipping - int surfaceHeight = renderSurface->height; - int clip_y0, clip_y1; + LOG_DEBUG(Lib_Font, "{}", + formatParams({ + Param("renderSurface", renderSurface), + Param("x0", x0), + Param("y0", y0), + Param("w", w), + Param("h", h), + })); - if (surfaceHeight != 0) { - if (y0 < 0) { // Adjust for negative y0 - clip_y0 = 0; - clip_y1 = (h + y0 > surfaceHeight) ? surfaceHeight : h + y0; - if (h <= -y0) - clip_y1 = 0; // Entire height is clipped + const u32 surface_w = static_cast(renderSurface->width); + if (surface_w != 0) { + u32 x1; + u32 x0_out; + u32 w_u = static_cast(w); + if (x0 < 0) { + x1 = w_u + static_cast(x0); + if (surface_w < x1) { + x1 = surface_w; + } + if (w_u <= static_cast(-x0)) { + x1 = 0; + } + x0_out = 0; } else { - clip_y0 = (y0 > surfaceHeight) ? surfaceHeight : y0; - clip_y1 = (h + y0 > surfaceHeight) ? surfaceHeight : h + y0; + x1 = surface_w; + x0_out = surface_w; + if (static_cast(x0) <= surface_w) { + if (surface_w < w_u) { + w_u = surface_w; + } + x1 = w_u + static_cast(x0); + x0_out = static_cast(x0); + if (surface_w < x1) { + x1 = surface_w; + } + } } - renderSurface->sc_y0 = clip_y0; - renderSurface->sc_y1 = clip_y1; + renderSurface->sc_x0 = x0_out; + renderSurface->sc_x1 = x1; } + + const u32 surface_h = static_cast(renderSurface->height); + if (surface_h == 0) { + return; + } + + u32 y0_out; + u32 y1 = surface_h; + u32 h_u = static_cast(h); + if (y0 < 0) { + y0_out = 0; + if (h_u <= static_cast(-y0)) { + y1 = 0; + renderSurface->sc_y0 = 0; + renderSurface->sc_y1 = 0; + return; + } + } else { + y0_out = surface_h; + if (surface_h < static_cast(y0)) { + renderSurface->sc_y0 = y0_out; + renderSurface->sc_y1 = y1; + return; + } + y0_out = static_cast(y0); + if (surface_h < h_u) { + h_u = surface_h; + } + } + + const u32 y1_candidate = h_u + static_cast(y0); + if (y1_candidate <= surface_h) { + y1 = y1_candidate; + } + renderSurface->sc_y0 = y0_out; + renderSurface->sc_y1 = y1; } s32 PS4_SYSV_ABI sceFontRenderSurfaceSetStyleFrame(OrbisFontRenderSurface* renderSurface, OrbisFontStyleFrame* styleFrame) { + LOG_INFO(Lib_Font, "called"); + if (!renderSurface) { - LOG_ERROR(Lib_Font, "Invalid Parameter"); + LOG_ERROR(Lib_Font, "NULL_POINTER"); return ORBIS_FONT_ERROR_INVALID_PARAMETER; } - if (!styleFrame) { - renderSurface->styleFlag &= 0xFE; // Clear style flag - } else { - // Validate magic number - if (styleFrame->magic != 0xF09) { - LOG_ERROR(Lib_Font, "Invalid magic"); + { + auto* memory = Core::Memory::Instance(); + if (memory && !memory->IsValidMapping(reinterpret_cast(renderSurface), + sizeof(OrbisFontRenderSurface))) { + LOG_ERROR(Lib_Font, "INVALID_ADDR renderSurface={}", fmt::ptr(renderSurface)); + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + if (styleFrame && !memory->IsValidMapping(reinterpret_cast(styleFrame), + sizeof(OrbisFontStyleFrame))) { + LOG_ERROR(Lib_Font, "INVALID_ADDR styleFrame={}", fmt::ptr(styleFrame)); return ORBIS_FONT_ERROR_INVALID_PARAMETER; } - - renderSurface->styleFlag |= 1; // Set style flag } - // Assign style frame pointer - renderSurface->unkn_28[0] = styleFrame; - *(uint32_t*)(renderSurface->unkn_28 + 1) = 0; // Reset related field + LOG_DEBUG(Lib_Font, "{}", + formatParams({ + Param("renderSurface", renderSurface), + Param("styleFrame", styleFrame), + })); + if (!styleFrame) { + renderSurface->styleFlag &= ~u8{0x1}; + renderSurface->reserved_q[0] = 0; + renderSurface->reserved_q[1] = 0; + return ORBIS_OK; + } + if (styleFrame->magic != kStyleFrameMagic) { + LOG_ERROR(Lib_Font, "INVALID_PARAMETER"); + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + renderSurface->styleFlag |= 0x1; + renderSurface->reserved_q[0] = reinterpret_cast(styleFrame); + renderSurface->reserved_q[1] = 0; return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontSetEffectSlant() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceFontSetEffectSlant(OrbisFontHandle fontHandle, float slantRatio) { + LOG_INFO(Lib_Font, "called"); + LOG_DEBUG(Lib_Font, "{}", + formatParams({ + Param("fontHandle", fontHandle), + Param("slantRatio", slantRatio), + })); + + auto* font = GetNativeFont(fontHandle); + if (!font || font->magic != 0x0F02) { + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + u32 prev_lock = 0; + if (!AcquireFontLock(font, prev_lock)) { + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + if (Internal::StyleStateSetSlantRatio(font->style_frame, slantRatio) != 0) { + font->cached_style.layout_cache_state = 0; + } + ReleaseFontLock(font, prev_lock); return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontSetEffectWeight() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceFontSetEffectWeight(OrbisFontHandle fontHandle, float weightXScale, + float weightYScale, u32 mode) { + LOG_INFO(Lib_Font, "called"); + LOG_DEBUG(Lib_Font, "{}", + formatParams({ + Param("fontHandle", fontHandle), + Param("weightXScale", weightXScale), + Param("weightYScale", weightYScale), + Param("mode", mode), + })); + + if (mode != 0) { + LOG_ERROR(Lib_Font, "INVALID_PARAMETER"); + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + + auto* font = GetNativeFont(fontHandle); + if (!font || font->magic != 0x0F02) { + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + u32 prev_lock = 0; + if (!AcquireFontLock(font, prev_lock)) { + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + if (Internal::StyleStateSetWeightScale(font->style_frame, weightXScale, weightYScale) != 0) { + font->cached_style.layout_cache_state = 0; + } + ReleaseFontLock(font, prev_lock); return ORBIS_OK; } @@ -854,18 +5102,105 @@ s32 PS4_SYSV_ABI sceFontSetFontsOpenMode() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontSetResolutionDpi() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceFontSetResolutionDpi(OrbisFontHandle fontHandle, u32 h_dpi, u32 v_dpi) { + auto* font = GetNativeFont(fontHandle); + if (!font) { + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + u32 prev_font_lock = 0; + if (!AcquireFontLock(font, prev_font_lock)) { + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + if (Internal::StyleStateSetDpi(font->style_frame, h_dpi, v_dpi) != 0) { + font->cached_style.layout_cache_state = 0; + } + + if (auto* st = Internal::TryGetState(fontHandle)) { + st->dpi_x = font->style_frame[0]; + st->dpi_y = font->style_frame[1]; + st->ext_scale_for_height = 0.0f; + st->layout_cached = false; + } + + ReleaseFontLock(font, prev_font_lock); return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontSetScalePixel() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceFontSetScalePixel(OrbisFontHandle fontHandle, float w, float h) { + LOG_INFO(Lib_Font, "called"); + + if (!fontHandle) { + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + LOG_DEBUG(Lib_Font, "{}", + formatParams({Param("fontHandle", fontHandle), Param("w", w), Param("h", h)})); + + auto* font = GetNativeFont(fontHandle); + if (!font || font->magic != 0x0F02) { + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + u32 prev_font_lock = 0; + if (!AcquireFontLock(font, prev_font_lock)) { + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + if (Internal::StyleStateSetScalePixel(font->style_frame, w, h) != 0) { + font->cached_style.layout_cache_state = 0; + } + + if (auto* st = Internal::TryGetState(fontHandle)) { + st->scale_w = w; + st->scale_h = h; + st->scale_point_active = false; + st->ext_scale_for_height = 0.0f; + st->layout_cached = false; + } + ReleaseFontLock(font, prev_font_lock); return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontSetScalePoint() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceFontSetScalePoint(OrbisFontHandle fontHandle, float w, float h) { + LOG_INFO(Lib_Font, "called"); + + if (!fontHandle) { + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + LOG_DEBUG(Lib_Font, "{}", + formatParams({Param("fontHandle", fontHandle), Param("w", w), Param("h", h)})); + + auto* font = GetNativeFont(fontHandle); + if (!font || font->magic != 0x0F02) { + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + u32 prev_font_lock = 0; + if (!AcquireFontLock(font, prev_font_lock)) { + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + if (Internal::StyleStateSetScalePoint(font->style_frame, w, h) != 0) { + font->cached_style.layout_cache_state = 0; + } + + if (auto* st = Internal::TryGetState(fontHandle)) { + st->scale_point_w = w; + st->scale_point_h = h; + st->scale_point_active = true; + st->ext_scale_for_height = 0.0f; + st->layout_cached = false; + } + ReleaseFontLock(font, prev_font_lock); return ORBIS_OK; } @@ -879,24 +5214,178 @@ s32 PS4_SYSV_ABI sceFontSetTypographicDesign() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontSetupRenderEffectSlant() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceFontSetupRenderEffectSlant(OrbisFontHandle fontHandle, float slantRatio) { + LOG_INFO(Lib_Font, "called"); + + if (!fontHandle) { + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + LOG_DEBUG(Lib_Font, "{}", + formatParams({ + Param("fontHandle", fontHandle), + Param("slantRatio", slantRatio), + })); + + auto* font = GetNativeFont(fontHandle); + if (!font || font->magic != 0x0F02) { + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + u32 prev_cached_lock = 0; + if (!AcquireCachedStyleLock(font, prev_cached_lock)) { + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + s32 rc = ORBIS_FONT_ERROR_NOT_BOUND_RENDERER; + if (font->renderer_binding.renderer != nullptr) { + if (Internal::StyleStateSetSlantRatio(&font->cached_style, slantRatio) != 0) { + font->cached_style.cache_flags_and_direction &= 0xFFFF0000u; + } + rc = ORBIS_OK; + } + + ReleaseCachedStyleLock(font, prev_cached_lock); + if (rc != ORBIS_OK) { + if (rc == ORBIS_FONT_ERROR_NOT_BOUND_RENDERER) { + LOG_ERROR(Lib_Font, "NOT_BOUND"); + } else { + LOG_ERROR(Lib_Font, "FAILED"); + } + } + return rc; } -s32 PS4_SYSV_ABI sceFontSetupRenderEffectWeight() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceFontSetupRenderEffectWeight(OrbisFontHandle fontHandle, float weightXScale, + float weightYScale, u32 mode) { + LOG_INFO(Lib_Font, "called"); + + if (!fontHandle) { + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + LOG_DEBUG(Lib_Font, "{}", + formatParams({ + Param("fontHandle", fontHandle), + Param("weightXScale", weightXScale), + Param("weightYScale", weightYScale), + Param("mode", mode), + })); + + auto* font = GetNativeFont(fontHandle); + if (!font || font->magic != 0x0F02) { + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + u32 prev_cached_lock = 0; + if (!AcquireCachedStyleLock(font, prev_cached_lock)) { + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + s32 rc = ORBIS_FONT_ERROR_NOT_BOUND_RENDERER; + if (font->renderer_binding.renderer != nullptr) { + rc = ORBIS_FONT_ERROR_INVALID_PARAMETER; + if (mode == 0) { + if (Internal::StyleStateSetWeightScale(&font->cached_style, weightXScale, + weightYScale) != 0) { + font->cached_style.cache_flags_and_direction &= 0xFFFF0000u; + } + rc = ORBIS_OK; + } + } + + ReleaseCachedStyleLock(font, prev_cached_lock); + if (rc != ORBIS_OK) { + if (rc == ORBIS_FONT_ERROR_NOT_BOUND_RENDERER) { + LOG_ERROR(Lib_Font, "NOT_BOUND"); + } else if (rc == ORBIS_FONT_ERROR_INVALID_PARAMETER) { + LOG_ERROR(Lib_Font, "INVALID_PARAMETER"); + } else { + LOG_ERROR(Lib_Font, "FAILED"); + } + } + return rc; } -s32 PS4_SYSV_ABI sceFontSetupRenderScalePixel() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceFontSetupRenderScalePixel(OrbisFontHandle fontHandle, float w, float h) { + LOG_INFO(Lib_Font, "called"); + + if (!fontHandle) { + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + LOG_DEBUG(Lib_Font, "{}", + formatParams({Param("fontHandle", fontHandle), Param("w", w), Param("h", h)})); + + auto* font = GetNativeFont(fontHandle); + if (!font || font->magic != 0x0F02) { + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + u32 prev_cached_lock = 0; + if (!AcquireCachedStyleLock(font, prev_cached_lock)) { + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + s32 rc = ORBIS_FONT_ERROR_NOT_BOUND_RENDERER; + if (font->renderer_binding.renderer != nullptr) { + if (Internal::StyleStateSetScalePixel(&font->cached_style, w, h) != 0) { + font->cached_style.cache_flags_and_direction &= 0xFFFF0000u; + } + rc = ORBIS_OK; + if (auto* st = Internal::TryGetState(fontHandle)) { + st->render_scale_w = w; + st->render_scale_h = h; + st->render_scale_point_active = false; + } + } + + ReleaseCachedStyleLock(font, prev_cached_lock); + if (rc != ORBIS_OK) { + if (rc == ORBIS_FONT_ERROR_NOT_BOUND_RENDERER) { + LOG_ERROR(Lib_Font, "NOT_BOUND"); + } else { + LOG_ERROR(Lib_Font, "FAILED"); + } + } + return rc; } -s32 PS4_SYSV_ABI sceFontSetupRenderScalePoint() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceFontSetupRenderScalePoint(OrbisFontHandle fontHandle, float w, float h) { + auto* font = GetNativeFont(fontHandle); + if (!font) { + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + u32 prev_cached_lock = 0; + if (!AcquireCachedStyleLock(font, prev_cached_lock)) { + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + s32 rc = ORBIS_FONT_ERROR_NOT_BOUND_RENDERER; + if (font->renderer_binding.renderer != nullptr) { + if (Internal::StyleStateSetScalePoint(&font->cached_style, w, h) != 0) { + font->cached_style.cache_flags_and_direction &= 0xFFFF0000u; + } + rc = ORBIS_OK; + if (auto* st = Internal::TryGetState(fontHandle)) { + st->render_scale_point_w = w; + st->render_scale_point_h = h; + st->render_scale_point_active = true; + } + } + + ReleaseCachedStyleLock(font, prev_cached_lock); + return rc; } s32 PS4_SYSV_ABI sceFontStringGetTerminateCode() { @@ -926,29 +5415,15 @@ s32 PS4_SYSV_ABI sceFontStringRefersTextCharacters() { s32 PS4_SYSV_ABI sceFontStyleFrameGetEffectSlant(OrbisFontStyleFrame* styleFrame, float* slantRatio) { - if (!styleFrame) { - LOG_ERROR(Lib_Font, "Invalid Parameter"); + if (!styleFrame || styleFrame->magic != kStyleFrameMagic) { return ORBIS_FONT_ERROR_INVALID_PARAMETER; } - - // Validate the magic number - if (styleFrame->magic != 0xF09) { - LOG_ERROR(Lib_Font, "Invalid Magic"); - return ORBIS_FONT_ERROR_INVALID_PARAMETER; - } - - // Check if the slant effect is enabled (bit 1 in flags) - if (!(styleFrame->flags & 0x02)) { - LOG_ERROR(Lib_Font, "Flag not set"); + if ((styleFrame->flags1 & 2u) == 0) { return ORBIS_FONT_ERROR_UNSET_PARAMETER; } - if (!slantRatio) { - LOG_ERROR(Lib_Font, "Invalid Parameter"); return ORBIS_FONT_ERROR_INVALID_PARAMETER; } - - // Retrieve slant ratio *slantRatio = styleFrame->slantRatio; return ORBIS_OK; } @@ -956,131 +5431,322 @@ s32 PS4_SYSV_ABI sceFontStyleFrameGetEffectSlant(OrbisFontStyleFrame* styleFrame s32 PS4_SYSV_ABI sceFontStyleFrameGetEffectWeight(OrbisFontStyleFrame* fontStyleFrame, float* weightXScale, float* weightYScale, uint32_t* mode) { - if (!fontStyleFrame) { - LOG_ERROR(Lib_Font, "Invalid Parameter"); + if (!fontStyleFrame || fontStyleFrame->magic != kStyleFrameMagic) { return ORBIS_FONT_ERROR_INVALID_PARAMETER; } - - // Validate the magic number - if (fontStyleFrame->magic != 0xF09) { - LOG_ERROR(Lib_Font, "Magic not set"); - return ORBIS_FONT_ERROR_INVALID_PARAMETER; - } - - // Check if the weight effect is enabled (bit 2 in flags) - if (!(fontStyleFrame->flags & 0x04)) { - LOG_ERROR(Lib_Font, "Flag not set"); + if ((fontStyleFrame->flags1 & 4u) == 0) { return ORBIS_FONT_ERROR_UNSET_PARAMETER; } - - // Retrieve weight scales (default is +1.0 to maintain normal weight) if (weightXScale) { - *weightXScale = fontStyleFrame->weightXScale + 1.0f; + *weightXScale = fontStyleFrame->effectWeightX + 1.0f; } if (weightYScale) { - *weightYScale = fontStyleFrame->weightYScale + 1.0f; + *weightYScale = fontStyleFrame->effectWeightY + 1.0f; } - - // Reset mode if provided if (mode) { *mode = 0; } + if (!weightXScale && !weightYScale && !mode) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontStyleFrameGetResolutionDpi() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceFontStyleFrameGetResolutionDpi(const OrbisFontStyleFrame* styleFrame, + u32* h_dpi, u32* v_dpi) { + if (!styleFrame || styleFrame->magic != kStyleFrameMagic) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + if (h_dpi) { + *h_dpi = styleFrame->hDpi; + } else if (!v_dpi) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + if (v_dpi) { + *v_dpi = styleFrame->vDpi; + } return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontStyleFrameGetScalePixel(OrbisFontStyleFrame* styleFrame, float* w, +s32 PS4_SYSV_ABI sceFontStyleFrameGetScalePixel(const OrbisFontStyleFrame* styleFrame, float* w, float* h) { - if (!styleFrame) { - LOG_ERROR(Lib_Font, "Invalid Parameter"); + if (!styleFrame || styleFrame->magic != kStyleFrameMagic) { return ORBIS_FONT_ERROR_INVALID_PARAMETER; } - - if (styleFrame->magic != 0xF09) { - LOG_ERROR(Lib_Font, "Invalid magic"); - return ORBIS_FONT_ERROR_INVALID_PARAMETER; - } - - if (!(styleFrame->flags & 0x01)) { - LOG_ERROR(Lib_Font, "Scaling effect parameter not set"); + if ((styleFrame->flags1 & 1u) == 0) { return ORBIS_FONT_ERROR_UNSET_PARAMETER; } + if (!w && !h) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } - // Check if scaling is allowed - int isScalingEnabled = styleFrame->scalingFlag; + const u32 scale_unit = styleFrame->scaleUnit; if (w) { - *w = styleFrame->scaleWidth; - if (isScalingEnabled && styleFrame->dpiX) { - *w *= ((float)styleFrame->dpiX / 72.0f); + float out = styleFrame->scalePixelW; + if (scale_unit != 0 && styleFrame->hDpi != 0) { + out = out * (static_cast(styleFrame->hDpi) / kPointsPerInch); } + *w = out; } - if (h) { - *h = styleFrame->scaleHeight; - if (isScalingEnabled && styleFrame->dpiY) { - *h *= ((float)styleFrame->dpiY / 72.0f); + float out = styleFrame->scalePixelH; + if (scale_unit != 0 && styleFrame->vDpi != 0) { + out = out * (static_cast(styleFrame->vDpi) / kPointsPerInch); + } + *h = out; + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceFontStyleFrameGetScalePoint(const OrbisFontStyleFrame* styleFrame, float* w, + float* h) { + if (!styleFrame || styleFrame->magic != kStyleFrameMagic) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + if ((styleFrame->flags1 & 1u) == 0) { + return ORBIS_FONT_ERROR_UNSET_PARAMETER; + } + if (!w && !h) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + + const u32 scale_unit = styleFrame->scaleUnit; + if (w) { + float out = styleFrame->scalePixelW; + if (scale_unit == 0 && styleFrame->hDpi != 0) { + out = out * (kPointsPerInch / static_cast(styleFrame->hDpi)); + } + *w = out; + } + if (h) { + float out = styleFrame->scalePixelH; + if (scale_unit == 0 && styleFrame->vDpi != 0) { + out = out * (kPointsPerInch / static_cast(styleFrame->vDpi)); + } + *h = out; + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceFontStyleFrameInit(OrbisFontStyleFrame* styleFrame) { + if (!styleFrame) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + std::memset(reinterpret_cast(&styleFrame->layout_cache_bytes[0x08]), 0, 0x20); + std::memset(reinterpret_cast(&styleFrame->layout_cache_state), 0, 0x20); + std::memset(reinterpret_cast(&styleFrame->scaleUnit), 0, 0x20); + styleFrame->magic = kStyleFrameMagic; + styleFrame->flags1 = 0; + styleFrame->flags2 = 0; + styleFrame->hDpi = 0x48; + styleFrame->vDpi = 0x48; + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceFontStyleFrameSetEffectSlant(OrbisFontStyleFrame* styleFrame, + float slantRatio) { + if (!styleFrame || styleFrame->magic != kStyleFrameMagic) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + + float clamped = slantRatio; + if (clamped > 1.0f) { + clamped = 1.0f; + } else if (clamped < -1.0f) { + clamped = -1.0f; + } + if (styleFrame->slantRatio != clamped) { + styleFrame->slantRatio = clamped; + } + styleFrame->flags1 |= 2u; + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceFontStyleFrameSetEffectWeight(OrbisFontStyleFrame* styleFrame, + float weightXScale, float weightYScale, + u32 mode) { + if (!styleFrame || styleFrame->magic != kStyleFrameMagic) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + if (mode != 0) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + + constexpr float kClamp = 0.04f; + auto clamp_delta = [&](float delta) { + if (delta > kClamp) { + return kClamp; + } + if (delta < -kClamp) { + return -kClamp; + } + return delta; + }; + + const float dx = clamp_delta(weightXScale - 1.0f); + const float dy = clamp_delta(weightYScale - 1.0f); + if (styleFrame->effectWeightX != dx) { + styleFrame->effectWeightX = dx; + } + if (styleFrame->effectWeightY != dy) { + styleFrame->effectWeightY = dy; + } + styleFrame->flags1 |= 4u; + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceFontStyleFrameSetResolutionDpi(OrbisFontStyleFrame* styleFrame, u32 h_dpi, + u32 v_dpi) { + if (!styleFrame || styleFrame->magic != kStyleFrameMagic) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + if (h_dpi == 0) { + h_dpi = 0x48; + } + if (v_dpi == 0) { + v_dpi = 0x48; + } + styleFrame->hDpi = h_dpi; + styleFrame->vDpi = v_dpi; + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceFontStyleFrameSetScalePixel(OrbisFontStyleFrame* styleFrame, float w, float h) { + if (!styleFrame || styleFrame->magic != kStyleFrameMagic) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + styleFrame->scaleUnit = 0; + styleFrame->scalePixelW = w; + styleFrame->scalePixelH = h; + styleFrame->flags1 |= 1u; + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceFontStyleFrameSetScalePoint(OrbisFontStyleFrame* styleFrame, float w, float h) { + if (!styleFrame || styleFrame->magic != kStyleFrameMagic) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + styleFrame->scaleUnit = 1; + styleFrame->scalePixelW = w; + styleFrame->scalePixelH = h; + styleFrame->flags1 |= 1u; + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceFontStyleFrameUnsetEffectSlant(OrbisFontStyleFrame* styleFrame) { + if (!styleFrame || styleFrame->magic != kStyleFrameMagic) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + styleFrame->flags1 &= 0xfdu; + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceFontStyleFrameUnsetEffectWeight(OrbisFontStyleFrame* styleFrame) { + if (!styleFrame || styleFrame->magic != kStyleFrameMagic) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + styleFrame->flags1 &= 0xfbu; + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceFontStyleFrameUnsetScale(OrbisFontStyleFrame* styleFrame) { + if (!styleFrame || styleFrame->magic != kStyleFrameMagic) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + styleFrame->flags1 &= 0xfeu; + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceFontSupportExternalFonts(OrbisFontLib library, u32 fontMax, u32 formats) { + LOG_INFO(Lib_Font, "called"); + LOG_DEBUG(Lib_Font, "{}", + formatParams({ + Param("library", library), + Param("fontMax", fontMax), + Param("formats", formats), + })); + + if (!library) { + LOG_ERROR(Lib_Font, "INVALID_LIBRARY"); + return ORBIS_FONT_ERROR_INVALID_LIBRARY; + } + auto* lib = static_cast(library); + if (lib->magic != 0x0F01) { + LOG_ERROR(Lib_Font, "INVALID_LIBRARY"); + return ORBIS_FONT_ERROR_INVALID_LIBRARY; + } + u32 prev_lock_word = 0; + if (!AcquireLibraryLock(lib, prev_lock_word)) { + LOG_ERROR(Lib_Font, "INVALID_LIBRARY"); + return ORBIS_FONT_ERROR_INVALID_LIBRARY; + } + + const auto alloc_fn = + lib->alloc_vtbl ? reinterpret_cast(lib->alloc_vtbl[0]) : nullptr; + const auto free_fn = + lib->alloc_vtbl ? reinterpret_cast(lib->alloc_vtbl[1]) : nullptr; + + if (lib->external_fonts_ctx) { + ReleaseLibraryLock(lib, prev_lock_word); + LOG_ERROR(Lib_Font, "ALREADY_SPECIFIED"); + return ORBIS_FONT_ERROR_ALREADY_SPECIFIED; + } + + const u32 ctx_size = (fontMax << 6) | 0x20u; + void* ctx = alloc_fn(lib->alloc_ctx, ctx_size); + if (!ctx) { + ReleaseLibraryLock(lib, prev_lock_word); + LOG_ERROR(Lib_Font, "ALLOCATION_FAILED"); + return ORBIS_FONT_ERROR_ALLOCATION_FAILED; + } + std::memset(ctx, 0, ctx_size); + + auto* header = static_cast(ctx); + void* list_head = nullptr; + if (fontMax != 0) { + list_head = static_cast(ctx) + sizeof(Internal::FontCtxHeader); + } + if (fontMax != 0) { + auto* entry = static_cast(list_head); + for (u32 i = 0; i < fontMax; i++) { + std::memset(&entry[i], 0, sizeof(entry[i])); } } - return ORBIS_OK; -} + header->lock_word = 0; + header->max_entries = fontMax; + header->base = list_head; -s32 PS4_SYSV_ABI sceFontStyleFrameGetScalePoint() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); - return ORBIS_OK; -} + const auto* driver = + lib->sys_driver ? reinterpret_cast(lib->sys_driver) : nullptr; + const auto support_fn = driver ? driver->support_formats : nullptr; + if (!support_fn) { + free_fn(lib->alloc_ctx, ctx); + ReleaseLibraryLock(lib, prev_lock_word); + LOG_ERROR(Lib_Font, "INVALID_LIBRARY"); + return ORBIS_FONT_ERROR_INVALID_LIBRARY; + } + const s32 support_rc = support_fn(library, formats); + if (support_rc != ORBIS_OK) { + free_fn(lib->alloc_ctx, ctx); + ReleaseLibraryLock(lib, prev_lock_word); + LOG_ERROR(Lib_Font, "SUPPORT_FAILED"); + return support_rc; + } -s32 PS4_SYSV_ABI sceFontStyleFrameInit() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); - return ORBIS_OK; -} + lib->external_fonts_ctx = ctx; -s32 PS4_SYSV_ABI sceFontStyleFrameSetEffectSlant() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); - return ORBIS_OK; -} + auto& ls = GetLibState(library); + ls.alloc_ctx = lib->alloc_ctx; + ls.alloc_fn = alloc_fn; + ls.free_fn = free_fn; + ls.support_external = true; + ls.external_fontMax = fontMax; + ls.external_formats = formats; + ls.owned_external_fonts_ctx = ctx; + ls.owned_external_fonts_ctx_size = ctx_size; -s32 PS4_SYSV_ABI sceFontStyleFrameSetEffectWeight() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceFontStyleFrameSetResolutionDpi() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceFontStyleFrameSetScalePixel() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceFontStyleFrameSetScalePoint() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceFontStyleFrameUnsetEffectSlant() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceFontStyleFrameUnsetEffectWeight() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceFontStyleFrameUnsetScale() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); - return ORBIS_OK; -} - -s32 PS4_SYSV_ABI sceFontSupportExternalFonts() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); + ReleaseLibraryLock(lib, prev_lock_word); return ORBIS_OK; } @@ -1089,8 +5755,83 @@ s32 PS4_SYSV_ABI sceFontSupportGlyphs() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontSupportSystemFonts() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceFontSupportSystemFonts(OrbisFontLib library) { + LOG_INFO(Lib_Font, "called"); + LOG_DEBUG(Lib_Font, "{}", formatParams({Param("library", library)})); + + if (!library) { + LOG_ERROR(Lib_Font, "INVALID_LIBRARY"); + return ORBIS_FONT_ERROR_INVALID_LIBRARY; + } + auto* lib = static_cast(library); + if (lib->magic != 0x0F01) { + LOG_ERROR(Lib_Font, "INVALID_LIBRARY"); + return ORBIS_FONT_ERROR_INVALID_LIBRARY; + } + u32 prev_lock_word = 0; + if (!AcquireLibraryLock(lib, prev_lock_word)) { + LOG_ERROR(Lib_Font, "INVALID_LIBRARY"); + return ORBIS_FONT_ERROR_INVALID_LIBRARY; + } + + const auto alloc_fn = + lib->alloc_vtbl ? reinterpret_cast(lib->alloc_vtbl[0]) : nullptr; + const auto free_fn = + lib->alloc_vtbl ? reinterpret_cast(lib->alloc_vtbl[1]) : nullptr; + + if (lib->sysfonts_ctx) { + ReleaseLibraryLock(lib, prev_lock_word); + LOG_ERROR(Lib_Font, "ALREADY_SPECIFIED"); + return ORBIS_FONT_ERROR_ALREADY_SPECIFIED; + } + + constexpr u32 kSysCtxSize = 0x1020; + void* ctx = alloc_fn(lib->alloc_ctx, kSysCtxSize); + if (!ctx) { + ReleaseLibraryLock(lib, prev_lock_word); + LOG_ERROR(Lib_Font, "ALLOCATION_FAILED"); + return ORBIS_FONT_ERROR_ALLOCATION_FAILED; + } + std::memset(ctx, 0, kSysCtxSize); + + auto* header = static_cast(ctx); + header->lock_word = 0; + header->max_entries = 0x40; + header->base = reinterpret_cast(ctx) + sizeof(Internal::FontCtxHeader); + + auto* entries = static_cast(header->base); + for (u32 i = 0; i < header->max_entries; i++) { + std::memset(&entries[i], 0, sizeof(entries[i])); + } + + const auto* driver = + lib->sys_driver ? reinterpret_cast(lib->sys_driver) : nullptr; + const auto support_fn = driver ? driver->support_formats : nullptr; + if (!support_fn) { + free_fn(lib->alloc_ctx, ctx); + ReleaseLibraryLock(lib, prev_lock_word); + LOG_ERROR(Lib_Font, "INVALID_LIBRARY"); + return ORBIS_FONT_ERROR_INVALID_LIBRARY; + } + const s32 support_rc = support_fn(library, 0x52); + if (support_rc != ORBIS_OK) { + free_fn(lib->alloc_ctx, ctx); + ReleaseLibraryLock(lib, prev_lock_word); + LOG_ERROR(Lib_Font, "SUPPORT_FAILED"); + return support_rc; + } + + lib->sysfonts_ctx = ctx; + + auto& ls = GetLibState(library); + ls.alloc_ctx = lib->alloc_ctx; + ls.alloc_fn = alloc_fn; + ls.free_fn = free_fn; + ls.support_system = true; + ls.owned_sysfonts_ctx = ctx; + ls.owned_sysfonts_ctx_size = kSysCtxSize; + + ReleaseLibraryLock(lib, prev_lock_word); return ORBIS_OK; } @@ -1124,9 +5865,40 @@ s32 PS4_SYSV_ABI sceFontTextSourceSetWritingForm() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontUnbindRenderer() { - LOG_ERROR(Lib_Font, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceFontUnbindRenderer(OrbisFontHandle fontHandle) { + LOG_INFO(Lib_Font, "called"); + LOG_DEBUG(Lib_Font, "{}", formatParams({Param("fontHandle", fontHandle)})); + + auto* font = GetNativeFont(fontHandle); + if (!font) { + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + u32 prev_cached_lock = 0; + if (!AcquireCachedStyleLock(font, prev_cached_lock)) { + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + s32 rc = ORBIS_FONT_ERROR_NOT_BOUND_RENDERER; + if (font->renderer_binding.renderer != nullptr) { + rc = ORBIS_OK; + font->renderer_binding.renderer = nullptr; + if (auto* st = Internal::TryGetState(fontHandle)) { + st->bound_renderer = nullptr; + } + } + + ReleaseCachedStyleLock(font, prev_cached_lock); + if (rc != ORBIS_OK) { + if (rc == ORBIS_FONT_ERROR_NOT_BOUND_RENDERER) { + LOG_ERROR(Lib_Font, "NOT_BOUND"); + } else { + LOG_ERROR(Lib_Font, "FAILED"); + } + } + return rc; } s32 PS4_SYSV_ABI sceFontWordsFindWordCharacters() { @@ -1608,4 +6380,4 @@ void RegisterlibSceFont(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("-n5a6V0wWPU", "libSceFont", 1, "libSceFont", Func_FE7E5AE95D3058F5); }; -} // namespace Libraries::Font \ No newline at end of file +} // namespace Libraries::Font diff --git a/src/core/libraries/font/font.h b/src/core/libraries/font/font.h index a8e239126..2608a99fb 100644 --- a/src/core/libraries/font/font.h +++ b/src/core/libraries/font/font.h @@ -1,299 +1,589 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "common/types.h" - -namespace Core::Loader { -class SymbolsResolver; -} - -namespace Libraries::Font { - -struct OrbisFontTextCharacter { - // Other fields... - struct OrbisFontTextCharacter* next; // Pointer to the next node 0x00 - struct OrbisFontTextCharacter* prev; // Pointer to the next node 0x08 - void* textOrder; // Field at offset 0x10 (pointer to text order info) - u32 characterCode; // Field assumed at offset 0x28 - u8 unkn_0x31; // Offset 0x31 - u8 unkn_0x33; // Offset 0x33 - u8 charType; // Field assumed at offset 0x39 - u8 bidiLevel; // Field assumed at offset 0x3B stores the Bidi level - u8 formatFlags; // Field at offset 0x3D (stores format-related flags) -}; - -struct OrbisFontRenderSurface { - void* buffer; - s32 widthByte; - s8 pixelSizeByte; - u8 unkn_0xd; - u8 styleFlag; - u8 unkn_0xf; - s32 width, height; - u32 sc_x0; - u32 sc_y0; - u32 sc_x1; - u32 sc_y1; - void* unkn_28[3]; -}; - -struct OrbisFontStyleFrame { - /*0x00*/ u16 magic; // Expected to be 0xF09 - /*0x02*/ u16 flags; - /*0x04*/ s32 dpiX; // DPI scaling factor for width - /*0x08*/ s32 dpiY; // DPI scaling factor for height - /*0x0c*/ s32 scalingFlag; // Indicates whether scaling is enabled - /*0x10*/ - /*0x14*/ float scaleWidth; // Width scaling factor - /*0x18*/ float scaleHeight; // Height scaling factor - /*0x1c*/ float weightXScale; - /*0x20*/ float weightYScale; - /*0x24*/ float slantRatio; -}; - -s32 PS4_SYSV_ABI sceFontAttachDeviceCacheBuffer(); -s32 PS4_SYSV_ABI sceFontBindRenderer(); -s32 PS4_SYSV_ABI sceFontCharacterGetBidiLevel(OrbisFontTextCharacter* textCharacter, - int* bidiLevel); -s32 PS4_SYSV_ABI sceFontCharacterGetSyllableStringState(); -s32 PS4_SYSV_ABI sceFontCharacterGetTextFontCode(); -s32 PS4_SYSV_ABI sceFontCharacterGetTextOrder(OrbisFontTextCharacter* textCharacter, - void** pTextOrder); -u32 PS4_SYSV_ABI sceFontCharacterLooksFormatCharacters(OrbisFontTextCharacter* textCharacter); -u32 PS4_SYSV_ABI sceFontCharacterLooksWhiteSpace(OrbisFontTextCharacter* textCharacter); -OrbisFontTextCharacter* PS4_SYSV_ABI -sceFontCharacterRefersTextBack(OrbisFontTextCharacter* textCharacter); -OrbisFontTextCharacter* PS4_SYSV_ABI -sceFontCharacterRefersTextNext(OrbisFontTextCharacter* textCharacter); -s32 PS4_SYSV_ABI sceFontCharactersRefersTextCodes(); -s32 PS4_SYSV_ABI sceFontClearDeviceCache(); -s32 PS4_SYSV_ABI sceFontCloseFont(); -s32 PS4_SYSV_ABI sceFontControl(); -s32 PS4_SYSV_ABI sceFontCreateGraphicsDevice(); -s32 PS4_SYSV_ABI sceFontCreateGraphicsService(); -s32 PS4_SYSV_ABI sceFontCreateGraphicsServiceWithEdition(); -s32 PS4_SYSV_ABI sceFontCreateLibrary(); -s32 PS4_SYSV_ABI sceFontCreateLibraryWithEdition(); -s32 PS4_SYSV_ABI sceFontCreateRenderer(); -s32 PS4_SYSV_ABI sceFontCreateRendererWithEdition(); -s32 PS4_SYSV_ABI sceFontCreateString(); -s32 PS4_SYSV_ABI sceFontCreateWords(); -s32 PS4_SYSV_ABI sceFontCreateWritingLine(); -s32 PS4_SYSV_ABI sceFontDefineAttribute(); -s32 PS4_SYSV_ABI sceFontDeleteGlyph(); -s32 PS4_SYSV_ABI sceFontDestroyGraphicsDevice(); -s32 PS4_SYSV_ABI sceFontDestroyGraphicsService(); -s32 PS4_SYSV_ABI sceFontDestroyLibrary(); -s32 PS4_SYSV_ABI sceFontDestroyRenderer(); -s32 PS4_SYSV_ABI sceFontDestroyString(); -s32 PS4_SYSV_ABI sceFontDestroyWords(); -s32 PS4_SYSV_ABI sceFontDestroyWritingLine(); -s32 PS4_SYSV_ABI sceFontDettachDeviceCacheBuffer(); -s32 PS4_SYSV_ABI sceFontGenerateCharGlyph(); -s32 PS4_SYSV_ABI sceFontGetAttribute(); -s32 PS4_SYSV_ABI sceFontGetCharGlyphCode(); -s32 PS4_SYSV_ABI sceFontGetCharGlyphMetrics(); -s32 PS4_SYSV_ABI sceFontGetEffectSlant(); -s32 PS4_SYSV_ABI sceFontGetEffectWeight(); -s32 PS4_SYSV_ABI sceFontGetFontGlyphsCount(); -s32 PS4_SYSV_ABI sceFontGetFontGlyphsOutlineProfile(); -s32 PS4_SYSV_ABI sceFontGetFontMetrics(); -s32 PS4_SYSV_ABI sceFontGetFontResolution(); -s32 PS4_SYSV_ABI sceFontGetFontStyleInformation(); -s32 PS4_SYSV_ABI sceFontGetGlyphExpandBufferState(); -s32 PS4_SYSV_ABI sceFontGetHorizontalLayout(); -s32 PS4_SYSV_ABI sceFontGetKerning(); -s32 PS4_SYSV_ABI sceFontGetLibrary(); -s32 PS4_SYSV_ABI sceFontGetPixelResolution(); -s32 PS4_SYSV_ABI sceFontGetRenderCharGlyphMetrics(); -s32 PS4_SYSV_ABI sceFontGetRenderEffectSlant(); -s32 PS4_SYSV_ABI sceFontGetRenderEffectWeight(); -s32 PS4_SYSV_ABI sceFontGetRenderScaledKerning(); -s32 PS4_SYSV_ABI sceFontGetRenderScalePixel(); -s32 PS4_SYSV_ABI sceFontGetRenderScalePoint(); -s32 PS4_SYSV_ABI sceFontGetResolutionDpi(); -s32 PS4_SYSV_ABI sceFontGetScalePixel(); -s32 PS4_SYSV_ABI sceFontGetScalePoint(); -s32 PS4_SYSV_ABI sceFontGetScriptLanguage(); -s32 PS4_SYSV_ABI sceFontGetTypographicDesign(); -s32 PS4_SYSV_ABI sceFontGetVerticalLayout(); -s32 PS4_SYSV_ABI sceFontGlyphDefineAttribute(); -s32 PS4_SYSV_ABI sceFontGlyphGetAttribute(); -s32 PS4_SYSV_ABI sceFontGlyphGetGlyphForm(); -s32 PS4_SYSV_ABI sceFontGlyphGetMetricsForm(); -s32 PS4_SYSV_ABI sceFontGlyphGetScalePixel(); -s32 PS4_SYSV_ABI sceFontGlyphRefersMetrics(); -s32 PS4_SYSV_ABI sceFontGlyphRefersMetricsHorizontal(); -s32 PS4_SYSV_ABI sceFontGlyphRefersMetricsHorizontalAdvance(); -s32 PS4_SYSV_ABI sceFontGlyphRefersMetricsHorizontalX(); -s32 PS4_SYSV_ABI sceFontGlyphRefersOutline(); -s32 PS4_SYSV_ABI sceFontGlyphRenderImage(); -s32 PS4_SYSV_ABI sceFontGlyphRenderImageHorizontal(); -s32 PS4_SYSV_ABI sceFontGlyphRenderImageVertical(); -s32 PS4_SYSV_ABI sceFontGraphicsBeginFrame(); -s32 PS4_SYSV_ABI sceFontGraphicsDrawingCancel(); -s32 PS4_SYSV_ABI sceFontGraphicsDrawingFinish(); -s32 PS4_SYSV_ABI sceFontGraphicsEndFrame(); -s32 PS4_SYSV_ABI sceFontGraphicsExchangeResource(); -s32 PS4_SYSV_ABI sceFontGraphicsFillMethodInit(); -s32 PS4_SYSV_ABI sceFontGraphicsFillPlotInit(); -s32 PS4_SYSV_ABI sceFontGraphicsFillPlotSetLayout(); -s32 PS4_SYSV_ABI sceFontGraphicsFillPlotSetMapping(); -s32 PS4_SYSV_ABI sceFontGraphicsFillRatesInit(); -s32 PS4_SYSV_ABI sceFontGraphicsFillRatesSetFillEffect(); -s32 PS4_SYSV_ABI sceFontGraphicsFillRatesSetLayout(); -s32 PS4_SYSV_ABI sceFontGraphicsFillRatesSetMapping(); -s32 PS4_SYSV_ABI sceFontGraphicsGetDeviceUsage(); -s32 PS4_SYSV_ABI sceFontGraphicsRegionInit(); -s32 PS4_SYSV_ABI sceFontGraphicsRegionInitCircular(); -s32 PS4_SYSV_ABI sceFontGraphicsRegionInitRoundish(); -s32 PS4_SYSV_ABI sceFontGraphicsRelease(); -s32 PS4_SYSV_ABI sceFontGraphicsRenderResource(); -s32 PS4_SYSV_ABI sceFontGraphicsSetFramePolicy(); -s32 PS4_SYSV_ABI sceFontGraphicsSetupClipping(); -s32 PS4_SYSV_ABI sceFontGraphicsSetupColorRates(); -s32 PS4_SYSV_ABI sceFontGraphicsSetupFillMethod(); -s32 PS4_SYSV_ABI sceFontGraphicsSetupFillRates(); -s32 PS4_SYSV_ABI sceFontGraphicsSetupGlyphFill(); -s32 PS4_SYSV_ABI sceFontGraphicsSetupGlyphFillPlot(); -s32 PS4_SYSV_ABI sceFontGraphicsSetupHandleDefault(); -s32 PS4_SYSV_ABI sceFontGraphicsSetupLocation(); -s32 PS4_SYSV_ABI sceFontGraphicsSetupPositioning(); -s32 PS4_SYSV_ABI sceFontGraphicsSetupRotation(); -s32 PS4_SYSV_ABI sceFontGraphicsSetupScaling(); -s32 PS4_SYSV_ABI sceFontGraphicsSetupShapeFill(); -s32 PS4_SYSV_ABI sceFontGraphicsSetupShapeFillPlot(); -s32 PS4_SYSV_ABI sceFontGraphicsStructureCanvas(); -s32 PS4_SYSV_ABI sceFontGraphicsStructureCanvasSequence(); -s32 PS4_SYSV_ABI sceFontGraphicsStructureDesign(); -s32 PS4_SYSV_ABI sceFontGraphicsStructureDesignResource(); -s32 PS4_SYSV_ABI sceFontGraphicsStructureSurfaceTexture(); -s32 PS4_SYSV_ABI sceFontGraphicsUpdateClipping(); -s32 PS4_SYSV_ABI sceFontGraphicsUpdateColorRates(); -s32 PS4_SYSV_ABI sceFontGraphicsUpdateFillMethod(); -s32 PS4_SYSV_ABI sceFontGraphicsUpdateFillRates(); -s32 PS4_SYSV_ABI sceFontGraphicsUpdateGlyphFill(); -s32 PS4_SYSV_ABI sceFontGraphicsUpdateGlyphFillPlot(); -s32 PS4_SYSV_ABI sceFontGraphicsUpdateLocation(); -s32 PS4_SYSV_ABI sceFontGraphicsUpdatePositioning(); -s32 PS4_SYSV_ABI sceFontGraphicsUpdateRotation(); -s32 PS4_SYSV_ABI sceFontGraphicsUpdateScaling(); -s32 PS4_SYSV_ABI sceFontGraphicsUpdateShapeFill(); -s32 PS4_SYSV_ABI sceFontGraphicsUpdateShapeFillPlot(); -s32 PS4_SYSV_ABI sceFontMemoryInit(); -s32 PS4_SYSV_ABI sceFontMemoryTerm(); -s32 PS4_SYSV_ABI sceFontOpenFontFile(); -s32 PS4_SYSV_ABI sceFontOpenFontInstance(); -s32 PS4_SYSV_ABI sceFontOpenFontMemory(); -s32 PS4_SYSV_ABI sceFontOpenFontSet(); -s32 PS4_SYSV_ABI sceFontRebindRenderer(); -s32 PS4_SYSV_ABI sceFontRenderCharGlyphImage(); -s32 PS4_SYSV_ABI sceFontRenderCharGlyphImageHorizontal(); -s32 PS4_SYSV_ABI sceFontRenderCharGlyphImageVertical(); -s32 PS4_SYSV_ABI sceFontRendererGetOutlineBufferSize(); -s32 PS4_SYSV_ABI sceFontRendererResetOutlineBuffer(); -s32 PS4_SYSV_ABI sceFontRendererSetOutlineBufferPolicy(); -void PS4_SYSV_ABI sceFontRenderSurfaceInit(OrbisFontRenderSurface* renderSurface, void* buffer, - int bufWidthByte, int pixelSizeByte, int widthPixel, - int heightPixel); -void PS4_SYSV_ABI sceFontRenderSurfaceSetScissor(OrbisFontRenderSurface* renderSurface, int x0, - int y0, int w, int h); -s32 PS4_SYSV_ABI sceFontRenderSurfaceSetStyleFrame(OrbisFontRenderSurface* renderSurface, - OrbisFontStyleFrame* styleFrame); -s32 PS4_SYSV_ABI sceFontSetEffectSlant(); -s32 PS4_SYSV_ABI sceFontSetEffectWeight(); -s32 PS4_SYSV_ABI sceFontSetFontsOpenMode(); -s32 PS4_SYSV_ABI sceFontSetResolutionDpi(); -s32 PS4_SYSV_ABI sceFontSetScalePixel(); -s32 PS4_SYSV_ABI sceFontSetScalePoint(); -s32 PS4_SYSV_ABI sceFontSetScriptLanguage(); -s32 PS4_SYSV_ABI sceFontSetTypographicDesign(); -s32 PS4_SYSV_ABI sceFontSetupRenderEffectSlant(); -s32 PS4_SYSV_ABI sceFontSetupRenderEffectWeight(); -s32 PS4_SYSV_ABI sceFontSetupRenderScalePixel(); -s32 PS4_SYSV_ABI sceFontSetupRenderScalePoint(); -s32 PS4_SYSV_ABI sceFontStringGetTerminateCode(); -s32 PS4_SYSV_ABI sceFontStringGetTerminateOrder(); -s32 PS4_SYSV_ABI sceFontStringGetWritingForm(); -s32 PS4_SYSV_ABI sceFontStringRefersRenderCharacters(); -s32 PS4_SYSV_ABI sceFontStringRefersTextCharacters(); -s32 PS4_SYSV_ABI sceFontStyleFrameGetEffectSlant(OrbisFontStyleFrame* styleFrame, - float* slantRatio); -s32 PS4_SYSV_ABI sceFontStyleFrameGetEffectWeight(OrbisFontStyleFrame* fontStyleFrame, - float* weightXScale, float* weightYScale, - uint32_t* mode); -s32 PS4_SYSV_ABI sceFontStyleFrameGetResolutionDpi(); -s32 PS4_SYSV_ABI sceFontStyleFrameGetScalePixel(OrbisFontStyleFrame* styleFrame, float* w, - float* h); -s32 PS4_SYSV_ABI sceFontStyleFrameGetScalePoint(); -s32 PS4_SYSV_ABI sceFontStyleFrameInit(); -s32 PS4_SYSV_ABI sceFontStyleFrameSetEffectSlant(); -s32 PS4_SYSV_ABI sceFontStyleFrameSetEffectWeight(); -s32 PS4_SYSV_ABI sceFontStyleFrameSetResolutionDpi(); -s32 PS4_SYSV_ABI sceFontStyleFrameSetScalePixel(); -s32 PS4_SYSV_ABI sceFontStyleFrameSetScalePoint(); -s32 PS4_SYSV_ABI sceFontStyleFrameUnsetEffectSlant(); -s32 PS4_SYSV_ABI sceFontStyleFrameUnsetEffectWeight(); -s32 PS4_SYSV_ABI sceFontStyleFrameUnsetScale(); -s32 PS4_SYSV_ABI sceFontSupportExternalFonts(); -s32 PS4_SYSV_ABI sceFontSupportGlyphs(); -s32 PS4_SYSV_ABI sceFontSupportSystemFonts(); -s32 PS4_SYSV_ABI sceFontTextCodesStepBack(); -s32 PS4_SYSV_ABI sceFontTextCodesStepNext(); -s32 PS4_SYSV_ABI sceFontTextSourceInit(); -s32 PS4_SYSV_ABI sceFontTextSourceRewind(); -s32 PS4_SYSV_ABI sceFontTextSourceSetDefaultFont(); -s32 PS4_SYSV_ABI sceFontTextSourceSetWritingForm(); -s32 PS4_SYSV_ABI sceFontUnbindRenderer(); -s32 PS4_SYSV_ABI sceFontWordsFindWordCharacters(); -s32 PS4_SYSV_ABI sceFontWritingGetRenderMetrics(); -s32 PS4_SYSV_ABI sceFontWritingInit(); -s32 PS4_SYSV_ABI sceFontWritingLineClear(); -s32 PS4_SYSV_ABI sceFontWritingLineGetOrderingSpace(); -s32 PS4_SYSV_ABI sceFontWritingLineGetRenderMetrics(); -s32 PS4_SYSV_ABI sceFontWritingLineRefersRenderStep(); -s32 PS4_SYSV_ABI sceFontWritingLineWritesOrder(); -s32 PS4_SYSV_ABI sceFontWritingRefersRenderStep(); -s32 PS4_SYSV_ABI sceFontWritingRefersRenderStepCharacter(); -s32 PS4_SYSV_ABI sceFontWritingSetMaskInvisible(); -s32 PS4_SYSV_ABI Func_00F4D778F1C88CB3(); -s32 PS4_SYSV_ABI Func_03C650025FBB0DE7(); -s32 PS4_SYSV_ABI Func_07EAB8A163B27E1A(); -s32 PS4_SYSV_ABI Func_09408E88E4F97CE3(); -s32 PS4_SYSV_ABI Func_09F92905ED82A814(); -s32 PS4_SYSV_ABI Func_0D142CEE1AB21ABE(); -s32 PS4_SYSV_ABI Func_14BD2E9E119C16F2(); -s32 PS4_SYSV_ABI Func_1AC53C9EDEAE8D75(); -s32 PS4_SYSV_ABI Func_1D401185D5E24C3D(); -s32 PS4_SYSV_ABI Func_1E83CD20C2CC996F(); -s32 PS4_SYSV_ABI Func_314B1F765B9FE78A(); -s32 PS4_SYSV_ABI Func_350E6725FEDE29E1(); -s32 PS4_SYSV_ABI Func_3DB773F0A604BF39(); -s32 PS4_SYSV_ABI Func_4FF49DD21E311B1C(); -s32 PS4_SYSV_ABI Func_526287664A493981(); -s32 PS4_SYSV_ABI Func_55CA718DBC84A6E9(); -s32 PS4_SYSV_ABI Func_563FC5F0706A8B4D(); -s32 PS4_SYSV_ABI Func_569E2ECD34290F45(); -s32 PS4_SYSV_ABI Func_5A04775B6BE47685(); -s32 PS4_SYSV_ABI Func_5FD93BCAB6F79750(); -s32 PS4_SYSV_ABI Func_62B5398F864BD3B4(); -s32 PS4_SYSV_ABI Func_6F9010294D822367(); -s32 PS4_SYSV_ABI Func_7757E947423A7A67(); -s32 PS4_SYSV_ABI Func_7E06BA52077F54FA(); -s32 PS4_SYSV_ABI Func_93B36DEA021311D6(); -s32 PS4_SYSV_ABI Func_94B0891E7111598A(); -s32 PS4_SYSV_ABI Func_9785C9128C2FE7CD(); -s32 PS4_SYSV_ABI Func_97DFBC9B65FBC0E1(); -s32 PS4_SYSV_ABI Func_ACD9717405D7D3CA(); -s32 PS4_SYSV_ABI Func_B19A8AEC3FD4F16F(); -s32 PS4_SYSV_ABI Func_C10F488AD7CF103D(); -s32 PS4_SYSV_ABI Func_D0C8B5FF4A6826C7(); -s32 PS4_SYSV_ABI Func_E48D3CD01C342A33(); -s32 PS4_SYSV_ABI Func_EAC96B2186B71E14(); -s32 PS4_SYSV_ABI Func_FE4788A96EF46256(); -s32 PS4_SYSV_ABI Func_FE7E5AE95D3058F5(); - -void RegisterlibSceFont(Core::Loader::SymbolsResolver* sym); -} // namespace Libraries::Font \ No newline at end of file +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once +#include +#include "common/types.h" +namespace Core::Loader { +class SymbolsResolver; +} +namespace Libraries::Font { + +struct OrbisFontHandleOpaque { + u32 reserved[64]; +}; + +using OrbisFontLib = void*; +using OrbisFontHandle = OrbisFontHandleOpaque*; +using OrbisFontRendererCreateParams = void*; +using OrbisFontRenderer = void*; +using OrbisFontLibCreateParams = void*; + +struct OrbisFontMem; + +struct OrbisFontOpenParams { + u16 tag; + u16 pad16; + u32 flags; + u32 subfont_index; + s32 unique_id; + const void* reserved_ptr2; + const void* reserved_ptr1; +}; + +// Fontlib APIs use Unicode scalar values (UTF-32 codepoints) in `u32` parameters such as `code` and +// `codepoint`. Codepoint-to-glyph resolution is performed via FreeType charmaps, with additional +// sysfont mapping and fallback behavior handled internally. +struct OrbisFontGlyphMetrics { + float width; + float height; + struct { + float bearingX; + float bearingY; + float advance; + } Horizontal; + struct { + float bearingX; + float bearingY; + float advance; + } Vertical; +}; + +struct OrbisFontGlyphMetricsHorizontal { + float width; + float height; + struct { + float bearing_x; + float bearing_y; + float advance; + } horizontal; +}; + +struct OrbisFontGlyphMetricsHorizontalX { + float width; + struct { + float bearing_x; + float advance; + } horizontal; +}; + +struct OrbisFontGlyphMetricsHorizontalAdvance { + struct { + float advance; + } horizontal; +}; + +struct OrbisFontKerning { + float offsetX; + float offsetY; + float positionX; + float positionY; +}; + +struct OrbisFontGlyphImageMetrics { + float bearingX; + float bearingY; + float advance; + float stride; + u32 width; + u32 height; +}; + +struct OrbisFontGenerateGlyphParams { + u16 id; + u16 res0; + // Bitmask copied into `OrbisFontGlyphOpaque::flags`. Only bits `0x01` and `0x10` are accepted + // by the public API; other bits are rejected as invalid. + u16 form_options; + u8 glyph_form; + u8 metrics_form; + const OrbisFontMem* mem; + void* res1; + void* res2; +}; + +struct OrbisFontGlyphOutlinePoint { + float x; + float y; +}; + +struct OrbisFontGlyphOutline { + s16 contours_cnt; + s16 points_cnt; + u32 outline_flags; + OrbisFontGlyphOutlinePoint* points_ptr; + u8* tags_ptr; + u16* contour_end_idx; +}; + +struct OrbisFontGlyphOpaque { + u16 magic; + // Encoded flags copied from `OrbisFontGenerateGlyphParams::form_options`. + u16 flags; + u8 glyph_form; + u8 metrics_form; + u16 em_size; + u16 baseline; + u16 height_px; + u16 origin_x; + u16 origin_y; + float scale_x; + float base_scale; + const OrbisFontMem* memory; +}; + +using OrbisFontGlyph = OrbisFontGlyphOpaque*; + +struct OrbisFontResultStage { + u8* p_00; + u32 u32_08; + u32 u32_0C; + u32 u32_10; +}; + +struct OrbisFontSurfaceImage { + u8* address; + u32 widthByte; + u8 pixelSizeByte; + u8 pixelFormat; + u16 pad16; +}; + +struct OrbisFontRenderOutput { + const OrbisFontResultStage* stage; + OrbisFontSurfaceImage SurfaceImage; + struct { + u32 x; + u32 y; + u32 w; + u32 h; + } UpdateRect; + OrbisFontGlyphImageMetrics ImageMetrics; +}; + +using OrbisFontAllocFn = void*(void* object, u32 size); +using OrbisFontFreeFn = void(void* object, void* p); +using OrbisFontReallocFn = void*(void* object, void* p, u32 newSize); +using OrbisFontCallocFn = void*(void* object, u32 nBlock, u32 size); +using OrbisFontMspaceCreateFn = void*(void* parent, const char* name, void* address, u32 size, + u32 attr); +using OrbisFontMspaceDestroyFn = void(void* parent, void* mspace); +using OrbisFontMemDestroyFn = void(OrbisFontMem* fontMemory, void* object, void* destroyArg); + +using OrbisFontAllocCb = OrbisFontAllocFn*; +using OrbisFontFreeCb = OrbisFontFreeFn*; +using OrbisFontReallocCb = OrbisFontReallocFn*; +using OrbisFontCallocCb = OrbisFontCallocFn*; +using OrbisFontMspaceCreateCb = OrbisFontMspaceCreateFn*; +using OrbisFontMspaceDestroyCb = OrbisFontMspaceDestroyFn*; +using OrbisFontMemDestroyCb = OrbisFontMemDestroyFn*; + +struct OrbisFontMemInterface { + OrbisFontAllocCb alloc{}; + OrbisFontFreeCb dealloc{}; + OrbisFontReallocCb realloc_fn{}; + OrbisFontCallocCb calloc_fn{}; + OrbisFontMspaceCreateCb mspace_create{}; + OrbisFontMspaceDestroyCb mspace_destroy{}; +}; + +struct OrbisFontMem { + u16 mem_kind; + u16 attr_bits; + u32 region_size; + void* region_base; + void* mspace_handle; + const OrbisFontMemInterface* iface; + OrbisFontMemDestroyCb on_destroy; + void* destroy_ctx; + void* some_ctx1; + void* some_ctx2; +}; + +struct OrbisFontTextCharacter { + struct OrbisFontTextCharacter* next; + struct OrbisFontTextCharacter* prev; + void* textOrder; + u32 characterCode; + u8 unknown_0x31; + u8 unknown_0x33; + u8 charType; + u8 bidiLevel; + u8 formatFlags; +}; + +struct OrbisFontRenderSurface { + void* buffer; + s32 widthByte; + s8 pixelSizeByte; + u8 pad0; + u8 styleFlag; + u8 pad2; + s32 width, height; + u32 sc_x0; + u32 sc_y0; + u32 sc_x1; + u32 sc_y1; + u64 reserved_q[11]; +}; + +struct OrbisFontStyleFrame { + /*0x00*/ u16 magic; + // `flags1` controls which fields are considered valid overrides: + // - bit0: scale override present (scaleUnit/scalePixelW/scalePixelH) + // - bit1: slant override present (slantRatio) + // - bit2: weight override present (effectWeightX/effectWeightY) + /*0x02*/ u8 flags1; + /*0x03*/ u8 flags2; + /*0x04*/ u32 hDpi; + /*0x08*/ u32 vDpi; + // `scaleUnit` selects how scale fields are interpreted: + // - 0: pixel scale (`scalePixelW/scalePixelH`) + // - 1: point scale (converted via DPI by style-state getters) + /*0x0C*/ u32 scaleUnit; + /*0x10*/ float baseScale; + /*0x14*/ float scalePixelW; + /*0x18*/ float scalePixelH; + /*0x1C*/ float effectWeightX; + /*0x20*/ float effectWeightY; + /*0x24*/ float slantRatio; + /*0x28*/ u32 reserved_0x28; + /*0x2C*/ u32 layout_cache_state; + // Packed cache metadata: + // - bits [7:0] cache flags + // - bits [31:16] direction word + /*0x30*/ u32 cache_flags_and_direction; + /*0x34*/ u32 cache_lock_word; + /*0x38*/ u8 layout_cache_bytes[0x20]; + /*0x58*/ u32 reserved_0x58; + /*0x5C*/ u32 cached_scalar_bits; +}; + +static_assert(sizeof(OrbisFontStyleFrame) == 0x60, "OrbisFontStyleFrame size"); +static_assert(offsetof(OrbisFontStyleFrame, magic) == 0x00, "OrbisFontStyleFrame magic offset"); +static_assert(offsetof(OrbisFontStyleFrame, flags1) == 0x02, "OrbisFontStyleFrame flags1 offset"); +static_assert(offsetof(OrbisFontStyleFrame, hDpi) == 0x04, "OrbisFontStyleFrame hDpi offset"); +static_assert(offsetof(OrbisFontStyleFrame, vDpi) == 0x08, "OrbisFontStyleFrame vDpi offset"); +static_assert(offsetof(OrbisFontStyleFrame, scaleUnit) == 0x0C, + "OrbisFontStyleFrame scaleUnit offset"); +static_assert(offsetof(OrbisFontStyleFrame, scalePixelW) == 0x14, + "OrbisFontStyleFrame scalePixelW offset"); +static_assert(offsetof(OrbisFontStyleFrame, scalePixelH) == 0x18, + "OrbisFontStyleFrame scalePixelH offset"); +static_assert(offsetof(OrbisFontStyleFrame, cache_lock_word) == 0x34, + "OrbisFontStyleFrame cache_lock_word offset"); +static_assert(offsetof(OrbisFontStyleFrame, reserved_0x58) == 0x58, + "OrbisFontStyleFrame reserved_0x58 offset"); + +static_assert(sizeof(OrbisFontGlyphMetrics) == 0x20, "OrbisFontGlyphMetrics size"); +static_assert(sizeof(OrbisFontResultStage) == 0x18, "OrbisFontTransImage size"); +static_assert(sizeof(OrbisFontSurfaceImage) == 0x10, "OrbisFontSurfaceImage size"); +static_assert(sizeof(OrbisFontRenderOutput) == 0x40, "OrbisFontRenderOutput size"); + +struct OrbisFontHorizontalLayout { + float baselineOffset; + float lineAdvance; + float decorationExtent; +}; +struct OrbisFontVerticalLayout { + float baselineOffsetX; + float columnAdvance; + float decorationSpan; +}; + +#if INTPTR_MAX == INT64_MAX +static_assert(sizeof(OrbisFontOpenParams) == 32, "OrbisFontOpenParams size"); +static_assert(sizeof(OrbisFontGlyphMetrics) == 32, "OrbisFontGlyphMetrics size"); +static_assert(sizeof(OrbisFontKerning) == 16, "OrbisFontKerning size"); +static_assert(sizeof(OrbisFontRenderSurface) == 128, "OrbisFontRenderSurface ABI size"); +static_assert(sizeof(OrbisFontGlyphImageMetrics) == 24, "OrbisFontGlyphImageMetrics ABI size"); +static_assert(sizeof(OrbisFontRenderOutput) == 64, "OrbisFontRenderOutput ABI size"); +static_assert(sizeof(OrbisFontMem) == 64, "OrbisFontMem ABI size"); +#endif + +s32 PS4_SYSV_ABI sceFontAttachDeviceCacheBuffer(OrbisFontLib library, void* buffer, u32 size); +s32 PS4_SYSV_ABI sceFontBindRenderer(OrbisFontHandle fontHandle, OrbisFontRenderer renderer); +s32 PS4_SYSV_ABI sceFontCharacterGetBidiLevel(OrbisFontTextCharacter* textCharacter, + int* bidiLevel); +s32 PS4_SYSV_ABI sceFontCharacterGetSyllableStringState(); +s32 PS4_SYSV_ABI sceFontCharacterGetTextFontCode(); +s32 PS4_SYSV_ABI sceFontCharacterGetTextOrder(OrbisFontTextCharacter* textCharacter, + void** pTextOrder); +u32 PS4_SYSV_ABI sceFontCharacterLooksFormatCharacters(OrbisFontTextCharacter* textCharacter); +u32 PS4_SYSV_ABI sceFontCharacterLooksWhiteSpace(OrbisFontTextCharacter* textCharacter); +OrbisFontTextCharacter* PS4_SYSV_ABI +sceFontCharacterRefersTextBack(OrbisFontTextCharacter* textCharacter); +OrbisFontTextCharacter* PS4_SYSV_ABI +sceFontCharacterRefersTextNext(OrbisFontTextCharacter* textCharacter); +s32 PS4_SYSV_ABI sceFontCharactersRefersTextCodes(); +s32 PS4_SYSV_ABI sceFontClearDeviceCache(OrbisFontLib library); +s32 PS4_SYSV_ABI sceFontCloseFont(); +s32 PS4_SYSV_ABI sceFontControl(); +s32 PS4_SYSV_ABI sceFontCreateGraphicsDevice(); +s32 PS4_SYSV_ABI sceFontCreateGraphicsService(); +s32 PS4_SYSV_ABI sceFontCreateGraphicsServiceWithEdition(); +s32 PS4_SYSV_ABI sceFontCreateLibrary(const OrbisFontMem* memory, + OrbisFontLibCreateParams create_params, + OrbisFontLib* pLibrary); +s32 PS4_SYSV_ABI sceFontCreateLibraryWithEdition(const OrbisFontMem* memory, + OrbisFontLibCreateParams create_params, + u64 edition, OrbisFontLib* pLibrary); +s32 PS4_SYSV_ABI sceFontCreateRenderer(const OrbisFontMem* memory, + OrbisFontRendererCreateParams create_params, + OrbisFontRenderer* pRenderer); +s32 PS4_SYSV_ABI sceFontCreateRendererWithEdition(const OrbisFontMem* memory, + OrbisFontRendererCreateParams create_params, + u64 edition, OrbisFontRenderer* pRenderer); +s32 PS4_SYSV_ABI sceFontCreateString(); +s32 PS4_SYSV_ABI sceFontCreateWords(); +s32 PS4_SYSV_ABI sceFontCreateWritingLine(); +s32 PS4_SYSV_ABI sceFontDefineAttribute(); +s32 PS4_SYSV_ABI sceFontDeleteGlyph(const OrbisFontMem* memory, OrbisFontGlyph* glyph); +s32 PS4_SYSV_ABI sceFontDestroyGraphicsDevice(); +s32 PS4_SYSV_ABI sceFontDestroyGraphicsService(); +s32 PS4_SYSV_ABI sceFontDestroyLibrary(OrbisFontLib* pLibrary); +s32 PS4_SYSV_ABI sceFontDestroyRenderer(OrbisFontRenderer* pRenderer); +s32 PS4_SYSV_ABI sceFontDestroyString(); +s32 PS4_SYSV_ABI sceFontDestroyWords(); +s32 PS4_SYSV_ABI sceFontDestroyWritingLine(); +s32 PS4_SYSV_ABI sceFontDettachDeviceCacheBuffer(); +s32 PS4_SYSV_ABI sceFontGenerateCharGlyph(OrbisFontHandle glyph_handle, u32 codepoint, + const OrbisFontGenerateGlyphParams* gen_params, + OrbisFontGlyph* glyph_out); +s32 PS4_SYSV_ABI sceFontGetAttribute(); +s32 PS4_SYSV_ABI sceFontGetCharGlyphCode(); +s32 PS4_SYSV_ABI sceFontGetCharGlyphMetrics(OrbisFontHandle fontHandle, u32 code, + OrbisFontGlyphMetrics* metrics); +s32 PS4_SYSV_ABI sceFontGetEffectSlant(OrbisFontHandle fontHandle, float* slantRatio); +s32 PS4_SYSV_ABI sceFontGetEffectWeight(OrbisFontHandle fontHandle, float* weightXScale, + float* weightYScale, u32* mode); +s32 PS4_SYSV_ABI sceFontGetFontGlyphsCount(); +s32 PS4_SYSV_ABI sceFontGetFontGlyphsOutlineProfile(); +s32 PS4_SYSV_ABI sceFontGetFontMetrics(); +s32 PS4_SYSV_ABI sceFontGetFontResolution(); +s32 PS4_SYSV_ABI sceFontGetFontStyleInformation(); +s32 PS4_SYSV_ABI sceFontGetGlyphExpandBufferState(); +s32 PS4_SYSV_ABI sceFontGetHorizontalLayout(OrbisFontHandle fontHandle, + OrbisFontHorizontalLayout* layout); +s32 PS4_SYSV_ABI sceFontGetKerning(OrbisFontHandle fontHandle, u32 preCode, u32 code, + OrbisFontKerning* kerning); +s32 PS4_SYSV_ABI sceFontGetLibrary(OrbisFontHandle fontHandle, OrbisFontLib* pLibrary); +s32 PS4_SYSV_ABI sceFontGetPixelResolution(); +s32 PS4_SYSV_ABI sceFontGetRenderCharGlyphMetrics(OrbisFontHandle fontHandle, u32 codepoint, + OrbisFontGlyphMetrics* out_metrics); +s32 PS4_SYSV_ABI sceFontGetRenderEffectSlant(OrbisFontHandle fontHandle, float* slantRatio); +s32 PS4_SYSV_ABI sceFontGetRenderEffectWeight(OrbisFontHandle fontHandle, float* weightXScale, + float* weightYScale, u32* mode); +s32 PS4_SYSV_ABI sceFontGetRenderScaledKerning(); +s32 PS4_SYSV_ABI sceFontGetRenderScalePixel(OrbisFontHandle fontHandle, float* out_w, float* out_h); +s32 PS4_SYSV_ABI sceFontGetRenderScalePoint(OrbisFontHandle fontHandle, float* out_w, float* out_h); +s32 PS4_SYSV_ABI sceFontGetResolutionDpi(); +s32 PS4_SYSV_ABI sceFontGetScalePixel(OrbisFontHandle fontHandle, float* out_w, float* out_h); +s32 PS4_SYSV_ABI sceFontGetScalePoint(OrbisFontHandle fontHandle, float* out_w, float* out_h); +s32 PS4_SYSV_ABI sceFontGetScriptLanguage(); +s32 PS4_SYSV_ABI sceFontGetTypographicDesign(); +s32 PS4_SYSV_ABI sceFontGetVerticalLayout(OrbisFontHandle fontHandle, + OrbisFontVerticalLayout* layout); +s32 PS4_SYSV_ABI sceFontGlyphDefineAttribute(); +s32 PS4_SYSV_ABI sceFontGlyphGetAttribute(); +s32 PS4_SYSV_ABI sceFontGlyphGetGlyphForm(); +s32 PS4_SYSV_ABI sceFontGlyphGetMetricsForm(); +s32 PS4_SYSV_ABI sceFontGlyphGetScalePixel(); +s32 PS4_SYSV_ABI sceFontGlyphRefersMetrics(); +s32 PS4_SYSV_ABI sceFontGlyphRefersMetricsHorizontal(); +s32 PS4_SYSV_ABI sceFontGlyphRefersMetricsHorizontalAdvance(); +s32 PS4_SYSV_ABI sceFontGlyphRefersMetricsHorizontalX(); +OrbisFontGlyphOutline* PS4_SYSV_ABI sceFontGlyphRefersOutline(OrbisFontGlyph glyph); +s32 PS4_SYSV_ABI sceFontGlyphRenderImage(); +s32 PS4_SYSV_ABI sceFontGlyphRenderImageHorizontal(); +s32 PS4_SYSV_ABI sceFontGlyphRenderImageVertical(); +s32 PS4_SYSV_ABI sceFontGraphicsBeginFrame(); +s32 PS4_SYSV_ABI sceFontGraphicsDrawingCancel(); +s32 PS4_SYSV_ABI sceFontGraphicsDrawingFinish(); +s32 PS4_SYSV_ABI sceFontGraphicsEndFrame(); +s32 PS4_SYSV_ABI sceFontGraphicsExchangeResource(); +s32 PS4_SYSV_ABI sceFontGraphicsFillMethodInit(); +s32 PS4_SYSV_ABI sceFontGraphicsFillPlotInit(); +s32 PS4_SYSV_ABI sceFontGraphicsFillPlotSetLayout(); +s32 PS4_SYSV_ABI sceFontGraphicsFillPlotSetMapping(); +s32 PS4_SYSV_ABI sceFontGraphicsFillRatesInit(); +s32 PS4_SYSV_ABI sceFontGraphicsFillRatesSetFillEffect(); +s32 PS4_SYSV_ABI sceFontGraphicsFillRatesSetLayout(); +s32 PS4_SYSV_ABI sceFontGraphicsFillRatesSetMapping(); +s32 PS4_SYSV_ABI sceFontGraphicsGetDeviceUsage(); +s32 PS4_SYSV_ABI sceFontGraphicsRegionInit(); +s32 PS4_SYSV_ABI sceFontGraphicsRegionInitCircular(); +s32 PS4_SYSV_ABI sceFontGraphicsRegionInitRoundish(); +s32 PS4_SYSV_ABI sceFontGraphicsRelease(); +s32 PS4_SYSV_ABI sceFontGraphicsRenderResource(); +s32 PS4_SYSV_ABI sceFontGraphicsSetFramePolicy(); +s32 PS4_SYSV_ABI sceFontGraphicsSetupClipping(); +s32 PS4_SYSV_ABI sceFontGraphicsSetupColorRates(); +s32 PS4_SYSV_ABI sceFontGraphicsSetupFillMethod(); +s32 PS4_SYSV_ABI sceFontGraphicsSetupFillRates(); +s32 PS4_SYSV_ABI sceFontGraphicsSetupGlyphFill(); +s32 PS4_SYSV_ABI sceFontGraphicsSetupGlyphFillPlot(); +s32 PS4_SYSV_ABI sceFontGraphicsSetupHandleDefault(); +s32 PS4_SYSV_ABI sceFontGraphicsSetupLocation(); +s32 PS4_SYSV_ABI sceFontGraphicsSetupPositioning(); +s32 PS4_SYSV_ABI sceFontGraphicsSetupRotation(); +s32 PS4_SYSV_ABI sceFontGraphicsSetupScaling(); +s32 PS4_SYSV_ABI sceFontGraphicsSetupShapeFill(); +s32 PS4_SYSV_ABI sceFontGraphicsSetupShapeFillPlot(); +s32 PS4_SYSV_ABI sceFontGraphicsStructureCanvas(); +s32 PS4_SYSV_ABI sceFontGraphicsStructureCanvasSequence(); +s32 PS4_SYSV_ABI sceFontGraphicsStructureDesign(); +s32 PS4_SYSV_ABI sceFontGraphicsStructureDesignResource(); +s32 PS4_SYSV_ABI sceFontGraphicsStructureSurfaceTexture(); +s32 PS4_SYSV_ABI sceFontGraphicsUpdateClipping(); +s32 PS4_SYSV_ABI sceFontGraphicsUpdateColorRates(); +s32 PS4_SYSV_ABI sceFontGraphicsUpdateFillMethod(); +s32 PS4_SYSV_ABI sceFontGraphicsUpdateFillRates(); +s32 PS4_SYSV_ABI sceFontGraphicsUpdateGlyphFill(); +s32 PS4_SYSV_ABI sceFontGraphicsUpdateGlyphFillPlot(); +s32 PS4_SYSV_ABI sceFontGraphicsUpdateLocation(); +s32 PS4_SYSV_ABI sceFontGraphicsUpdatePositioning(); +s32 PS4_SYSV_ABI sceFontGraphicsUpdateRotation(); +s32 PS4_SYSV_ABI sceFontGraphicsUpdateScaling(); +s32 PS4_SYSV_ABI sceFontGraphicsUpdateShapeFill(); +s32 PS4_SYSV_ABI sceFontGraphicsUpdateShapeFillPlot(); +s32 PS4_SYSV_ABI sceFontMemoryInit(OrbisFontMem* mem_desc, void* region_addr, u32 region_size, + const OrbisFontMemInterface* iface, void* mspace_obj, + OrbisFontMemDestroyCb destroy_cb, void* destroy_ctx); +s32 PS4_SYSV_ABI sceFontMemoryTerm(OrbisFontMem* mem_desc); +s32 PS4_SYSV_ABI sceFontOpenFontFile(OrbisFontLib library, const char* guest_path, u32 open_mode, + const OrbisFontOpenParams* open_detail, + OrbisFontHandle* out_handle); +s32 PS4_SYSV_ABI sceFontOpenFontInstance(OrbisFontHandle fontHandle, OrbisFontHandle templateFont, + OrbisFontHandle* pFontHandle); +s32 PS4_SYSV_ABI sceFontOpenFontMemory(OrbisFontLib library, const void* fontAddress, u32 fontSize, + const OrbisFontOpenParams* open_params, + OrbisFontHandle* pFontHandle); +s32 PS4_SYSV_ABI sceFontOpenFontSet(OrbisFontLib library, u32 fontSetType, u32 openMode, + const OrbisFontOpenParams* open_params, + OrbisFontHandle* pFontHandle); +s32 PS4_SYSV_ABI sceFontRebindRenderer(OrbisFontHandle fontHandle); +s32 PS4_SYSV_ABI sceFontRenderCharGlyphImage(OrbisFontHandle fontHandle, u32 code, + OrbisFontRenderSurface* surf, float x, float y, + OrbisFontGlyphMetrics* metrics, + OrbisFontRenderOutput* result); +s32 PS4_SYSV_ABI sceFontRenderCharGlyphImageHorizontal(OrbisFontHandle fontHandle, u32 code, + OrbisFontRenderSurface* surf, float x, + float y, OrbisFontGlyphMetrics* metrics, + OrbisFontRenderOutput* result); +s32 PS4_SYSV_ABI sceFontRenderCharGlyphImageVertical(OrbisFontHandle fontHandle, u32 code, + OrbisFontRenderSurface* surf, float x, float y, + OrbisFontGlyphMetrics* metrics, + OrbisFontRenderOutput* result); +s32 PS4_SYSV_ABI sceFontRendererGetOutlineBufferSize(OrbisFontRenderer fontRenderer, u32* size); +s32 PS4_SYSV_ABI sceFontRendererResetOutlineBuffer(OrbisFontRenderer fontRenderer); +s32 PS4_SYSV_ABI sceFontRendererSetOutlineBufferPolicy(OrbisFontRenderer fontRenderer, + u64 bufferPolicy, u32 basalSize, + u32 limitSize); +void PS4_SYSV_ABI sceFontRenderSurfaceInit(OrbisFontRenderSurface* renderSurface, void* buffer, + int bufWidthByte, int pixelSizeByte, int widthPixel, + int heightPixel); +void PS4_SYSV_ABI sceFontRenderSurfaceSetScissor(OrbisFontRenderSurface* renderSurface, int x0, + int y0, int w, int h); +s32 PS4_SYSV_ABI sceFontRenderSurfaceSetStyleFrame(OrbisFontRenderSurface* renderSurface, + OrbisFontStyleFrame* styleFrame); +s32 PS4_SYSV_ABI sceFontSetEffectSlant(OrbisFontHandle fontHandle, float slantRatio); +s32 PS4_SYSV_ABI sceFontSetEffectWeight(OrbisFontHandle fontHandle, float weightXScale, + float weightYScale, u32 mode); +s32 PS4_SYSV_ABI sceFontSetFontsOpenMode(); +s32 PS4_SYSV_ABI sceFontSetResolutionDpi(OrbisFontHandle fontHandle, u32 h_dpi, u32 v_dpi); +s32 PS4_SYSV_ABI sceFontSetScalePixel(OrbisFontHandle fontHandle, float w, float h); +s32 PS4_SYSV_ABI sceFontSetScalePoint(OrbisFontHandle fontHandle, float w, float h); +s32 PS4_SYSV_ABI sceFontSetScriptLanguage(); +s32 PS4_SYSV_ABI sceFontSetTypographicDesign(); +s32 PS4_SYSV_ABI sceFontSetupRenderEffectSlant(OrbisFontHandle fontHandle, float slantRatio); +s32 PS4_SYSV_ABI sceFontSetupRenderEffectWeight(OrbisFontHandle fontHandle, float weightXScale, + float weightYScale, u32 mode); +s32 PS4_SYSV_ABI sceFontSetupRenderScalePixel(OrbisFontHandle fontHandle, float w, float h); +s32 PS4_SYSV_ABI sceFontSetupRenderScalePoint(OrbisFontHandle fontHandle, float w, float h); +s32 PS4_SYSV_ABI sceFontStringGetTerminateCode(); +s32 PS4_SYSV_ABI sceFontStringGetTerminateOrder(); +s32 PS4_SYSV_ABI sceFontStringGetWritingForm(); +s32 PS4_SYSV_ABI sceFontStringRefersRenderCharacters(); +s32 PS4_SYSV_ABI sceFontStringRefersTextCharacters(); +s32 PS4_SYSV_ABI sceFontStyleFrameGetEffectSlant(OrbisFontStyleFrame* styleFrame, + float* slantRatio); +s32 PS4_SYSV_ABI sceFontStyleFrameGetEffectWeight(OrbisFontStyleFrame* fontStyleFrame, + float* weightXScale, float* weightYScale, + uint32_t* mode); +s32 PS4_SYSV_ABI sceFontStyleFrameGetResolutionDpi(const OrbisFontStyleFrame* styleFrame, + u32* h_dpi, u32* v_dpi); +s32 PS4_SYSV_ABI sceFontStyleFrameGetScalePixel(const OrbisFontStyleFrame* styleFrame, float* w, + float* h); +s32 PS4_SYSV_ABI sceFontStyleFrameGetScalePoint(const OrbisFontStyleFrame* styleFrame, float* w, + float* h); +s32 PS4_SYSV_ABI sceFontStyleFrameInit(OrbisFontStyleFrame* styleFrame); +s32 PS4_SYSV_ABI sceFontStyleFrameSetEffectSlant(OrbisFontStyleFrame* styleFrame, float slantRatio); +s32 PS4_SYSV_ABI sceFontStyleFrameSetEffectWeight(OrbisFontStyleFrame* styleFrame, + float weightXScale, float weightYScale, u32 mode); +s32 PS4_SYSV_ABI sceFontStyleFrameSetResolutionDpi(OrbisFontStyleFrame* styleFrame, u32 h_dpi, + u32 v_dpi); +s32 PS4_SYSV_ABI sceFontStyleFrameSetScalePixel(OrbisFontStyleFrame* styleFrame, float w, float h); +s32 PS4_SYSV_ABI sceFontStyleFrameSetScalePoint(OrbisFontStyleFrame* styleFrame, float w, float h); +s32 PS4_SYSV_ABI sceFontStyleFrameUnsetEffectSlant(OrbisFontStyleFrame* styleFrame); +s32 PS4_SYSV_ABI sceFontStyleFrameUnsetEffectWeight(OrbisFontStyleFrame* styleFrame); +s32 PS4_SYSV_ABI sceFontStyleFrameUnsetScale(OrbisFontStyleFrame* styleFrame); +s32 PS4_SYSV_ABI sceFontSupportExternalFonts(OrbisFontLib library, u32 fontMax, u32 formats); +s32 PS4_SYSV_ABI sceFontSupportGlyphs(); +s32 PS4_SYSV_ABI sceFontSupportSystemFonts(OrbisFontLib library); +s32 PS4_SYSV_ABI sceFontTextCodesStepBack(); +s32 PS4_SYSV_ABI sceFontTextCodesStepNext(); +s32 PS4_SYSV_ABI sceFontTextSourceInit(); +s32 PS4_SYSV_ABI sceFontTextSourceRewind(); +s32 PS4_SYSV_ABI sceFontTextSourceSetDefaultFont(); +s32 PS4_SYSV_ABI sceFontTextSourceSetWritingForm(); +s32 PS4_SYSV_ABI sceFontUnbindRenderer(OrbisFontHandle fontHandle); +s32 PS4_SYSV_ABI sceFontWordsFindWordCharacters(); +s32 PS4_SYSV_ABI sceFontWritingGetRenderMetrics(); +s32 PS4_SYSV_ABI sceFontWritingInit(); +s32 PS4_SYSV_ABI sceFontWritingLineClear(); +s32 PS4_SYSV_ABI sceFontWritingLineGetOrderingSpace(); +s32 PS4_SYSV_ABI sceFontWritingLineGetRenderMetrics(); +s32 PS4_SYSV_ABI sceFontWritingLineRefersRenderStep(); +s32 PS4_SYSV_ABI sceFontWritingLineWritesOrder(); +s32 PS4_SYSV_ABI sceFontWritingRefersRenderStep(); +s32 PS4_SYSV_ABI sceFontWritingRefersRenderStepCharacter(); +s32 PS4_SYSV_ABI sceFontWritingSetMaskInvisible(); +s32 PS4_SYSV_ABI Func_00F4D778F1C88CB3(); +s32 PS4_SYSV_ABI Func_03C650025FBB0DE7(); +s32 PS4_SYSV_ABI Func_07EAB8A163B27E1A(); +s32 PS4_SYSV_ABI Func_09408E88E4F97CE3(); +s32 PS4_SYSV_ABI Func_09F92905ED82A814(); +s32 PS4_SYSV_ABI Func_0D142CEE1AB21ABE(); +s32 PS4_SYSV_ABI Func_14BD2E9E119C16F2(); +s32 PS4_SYSV_ABI Func_1AC53C9EDEAE8D75(); +s32 PS4_SYSV_ABI Func_1D401185D5E24C3D(); +s32 PS4_SYSV_ABI Func_1E83CD20C2CC996F(); +s32 PS4_SYSV_ABI Func_314B1F765B9FE78A(); +s32 PS4_SYSV_ABI Func_350E6725FEDE29E1(); +s32 PS4_SYSV_ABI Func_3DB773F0A604BF39(); +s32 PS4_SYSV_ABI Func_4FF49DD21E311B1C(); +s32 PS4_SYSV_ABI Func_526287664A493981(); +s32 PS4_SYSV_ABI Func_55CA718DBC84A6E9(); +s32 PS4_SYSV_ABI Func_563FC5F0706A8B4D(); +s32 PS4_SYSV_ABI Func_569E2ECD34290F45(); +s32 PS4_SYSV_ABI Func_5A04775B6BE47685(); +s32 PS4_SYSV_ABI Func_5FD93BCAB6F79750(); +s32 PS4_SYSV_ABI Func_62B5398F864BD3B4(); +s32 PS4_SYSV_ABI Func_6F9010294D822367(); +s32 PS4_SYSV_ABI Func_7757E947423A7A67(); +s32 PS4_SYSV_ABI Func_7E06BA52077F54FA(); +s32 PS4_SYSV_ABI Func_93B36DEA021311D6(); +s32 PS4_SYSV_ABI Func_94B0891E7111598A(); +s32 PS4_SYSV_ABI Func_9785C9128C2FE7CD(); +s32 PS4_SYSV_ABI Func_97DFBC9B65FBC0E1(); +s32 PS4_SYSV_ABI Func_ACD9717405D7D3CA(); +s32 PS4_SYSV_ABI Func_B19A8AEC3FD4F16F(); +s32 PS4_SYSV_ABI Func_C10F488AD7CF103D(); +s32 PS4_SYSV_ABI Func_D0C8B5FF4A6826C7(); +s32 PS4_SYSV_ABI Func_E48D3CD01C342A33(); +s32 PS4_SYSV_ABI Func_EAC96B2186B71E14(); +s32 PS4_SYSV_ABI Func_FE4788A96EF46256(); +s32 PS4_SYSV_ABI Func_FE7E5AE95D3058F5(); + +void RegisterlibSceFont(Core::Loader::SymbolsResolver* sym); +} // namespace Libraries::Font diff --git a/src/core/libraries/font/font_internal.cpp b/src/core/libraries/font/font_internal.cpp new file mode 100644 index 000000000..f364aba3e --- /dev/null +++ b/src/core/libraries/font/font_internal.cpp @@ -0,0 +1,1918 @@ +// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "font_internal.h" + +#include + +#include +#include FT_FREETYPE_H +#include FT_OUTLINE_H +#include FT_TRUETYPE_TABLES_H + +#include "core/libraries/font/fontft_internal.h" +#include "core/libraries/kernel/kernel.h" + +namespace Libraries::Font::Internal { + +Core::FileSys::MntPoints* g_mnt = Common::Singleton::Instance(); + +using Libraries::Font::OrbisFontGenerateGlyphParams; +using Libraries::Font::OrbisFontGlyph; +using Libraries::Font::OrbisFontGlyphMetrics; +using Libraries::Font::OrbisFontGlyphOpaque; +using Libraries::Font::OrbisFontGlyphOutline; +using Libraries::Font::OrbisFontGlyphOutlinePoint; +using Libraries::Font::OrbisFontMem; +using Libraries::Font::OrbisFontStyleFrame; + +std::unordered_map g_font_state; + +std::unordered_map g_library_state; + +namespace { +std::mutex g_state_mutex; +} // namespace + +std::unordered_map + g_style_for_surface; + +void* g_allocator_vtbl_stub[4] = {}; +std::uint8_t g_sys_driver_stub{}; +std::uint8_t g_fontset_registry_stub{}; +std::uint8_t g_sysfonts_ctx_stub{}; +std::uint8_t g_external_fonts_ctx_stub{}; +u32 g_device_cache_stub{}; + +std::string FormatNamedParams(std::initializer_list params) { + fmt::memory_buffer buffer; + fmt::format_to(std::back_inserter(buffer), "params:\n"); + for (const auto& p : params) { + fmt::format_to(std::back_inserter(buffer), "{}: {}\n", p.name, p.value); + } + return fmt::to_string(buffer); +} + +bool HasSfntTables(const std::vector& bytes); +FontState* TryGetState(Libraries::Font::OrbisFontHandle h); +std::string ReportSystemFaceRequest(FontState& st, Libraries::Font::OrbisFontHandle handle, + bool& attached_out); + +namespace { + +static FT_Library GetFreeTypeLibrary() { + static std::once_flag once; + static FT_Library lib = nullptr; + std::call_once(once, [] { + FT_Library created = nullptr; + if (FT_Init_FreeType(&created) != 0) { + created = nullptr; + } + lib = created; + }); + return lib; +} + +static constexpr FT_Int32 kFtLoadFlagsBase = + static_cast(FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP | FT_LOAD_VERTICAL_LAYOUT); + +static constexpr FT_Int32 kFtLoadFlagsRender = + static_cast(kFtLoadFlagsBase | FT_LOAD_RENDER); + +} // namespace + +FT_Face CreateFreeTypeFaceFromBytes(const unsigned char* data, std::size_t size, + u32 subfont_index) { + if (!data || size == 0) { + return nullptr; + } + FT_Library lib = GetFreeTypeLibrary(); + if (!lib) { + return nullptr; + } + FT_Face face = nullptr; + if (FT_New_Memory_Face(lib, reinterpret_cast(data), static_cast(size), + static_cast(subfont_index), &face) != 0) { + LOG_DEBUG(Lib_Font, "CreateFreeTypeFaceFromBytes: failed size={} subfont_index={}", size, + subfont_index); + return nullptr; + } + (void)FT_Select_Charmap(face, FT_ENCODING_UNICODE); + LOG_DEBUG(Lib_Font, "CreateFreeTypeFaceFromBytes: ok face={} size={} subfont_index={}", + reinterpret_cast(face), size, subfont_index); + return face; +} + +void DestroyFreeTypeFace(FT_Face& face) { + if (!face) { + return; + } + FT_Done_Face(face); + face = nullptr; +} + +namespace { + +static std::optional ResolveKnownSysFontAliasImpl( + const std::filesystem::path& sysfonts_dir, std::string_view ps4_filename) { + const auto resolve_existing = + [&](std::string_view filename) -> std::optional { + const std::filesystem::path file_path{std::string(filename)}; + std::error_code ec; + { + const auto candidate = sysfonts_dir / file_path; + if (std::filesystem::exists(candidate, ec)) { + return candidate; + } + } + { + const auto candidate = sysfonts_dir / "font" / file_path; + if (std::filesystem::exists(candidate, ec)) { + return candidate; + } + } + { + const auto candidate = sysfonts_dir / "font2" / file_path; + if (std::filesystem::exists(candidate, ec)) { + return candidate; + } + } + return std::nullopt; + }; + + static constexpr std::array, 41> kAliases = {{ + {"SST-EU-ROMAN-L.OTF", "SST-Light.otf"}, + {"SST-EU-ROMAN.OTF", "SST-Roman.otf"}, + {"SST-EU-ROMAN-M.OTF", "SST-Medium.otf"}, + {"SST-EU-ROMAN-R.OTF", "SST-Roman.otf"}, + {"SST-EU-ROMAN-I.OTF", "SST-Italic.otf"}, + {"SST-EU-ROMAN-B.OTF", "SST-Bold.otf"}, + {"SST-EU-ROMAN-BI.OTF", "SST-BoldItalic.otf"}, + {"SST-ITALIC-L.OTF", "SST-LightItalic.otf"}, + {"SST-ITALIC-R.OTF", "SST-Italic.otf"}, + {"SST-ITALIC-M.OTF", "SST-MediumItalic.otf"}, + {"SST-ITALIC-B.OTF", "SST-BoldItalic.otf"}, + {"SST-TYPEWRITER-R.OTF", "SSTTypewriter-Roman.otf"}, + {"SST-TYPEWRITER-B.OTF", "SSTTypewriter-Bd.otf"}, + {"SST-JPPRO-R.OTF", "SSTJpPro-Regular.otf"}, + {"SST-JPPRO-B.OTF", "SSTJpPro-Bold.otf"}, + {"SST-CNGB-HEI-R.TTF", "DFHEI5-SONY.ttf"}, + {"SST-ARIB-STD-B24-R.TTF", "SSTAribStdB24-Regular.ttf"}, + {"SST-ARABIC-R.OTF", "SSTArabic-Roman.otf"}, + {"SST-ARABIC-L.OTF", "SSTArabic-Light.otf"}, + {"SST-ARABIC-M.OTF", "SSTArabic-Medium.otf"}, + {"SST-ARABIC-B.OTF", "SSTArabic-Bold.otf"}, + {"SCE-EXT-HANGUL-L.OTF", "SCEPS4Yoongd-Light.otf"}, + {"SCE-EXT-HANGUL-R.OTF", "SCEPS4Yoongd-Medium.otf"}, + {"SCE-EXT-HANGUL-B.OTF", "SCEPS4Yoongd-Bold.otf"}, + {"SSTCC-SERIF-MONO.OTF", "e046323ms.ttf"}, + {"SSTCC-SERIF.OTF", "e046323ts.ttf"}, + {"SSTCC-SANSSERIF-MONO.OTF", "n023055ms.ttf"}, + {"SSTCC-SANSSERIF.OTF", "n023055ts.ttf"}, + {"SSTCC-CUSUAL.OTF", "d013013ds.ttf"}, + {"SSTCC-CURSIVE.OTF", "k006004ds.ttf"}, + {"SSTCC-SMALLCAPITAL.OTF", "c041056ts.ttf"}, + {"SCE-JP-CATTLEYA-L.OTF", "SCE-RDC-R-JPN.otf"}, + {"SCE-JP-CATTLEYA-B.OTF", "SCE-RDC-B-JPN.otf"}, + {"SST-THAI-L.OTF", "SSTThai-Light.otf"}, + {"SST-THAI-R.OTF", "SSTThai-Roman.otf"}, + {"SST-THAI-M.OTF", "SSTThai-Medium.otf"}, + {"SST-THAI-B.OTF", "SSTThai-Bold.otf"}, + {"SST-VIETNAMESE-L.OTF", "SSTVietnamese-Light.otf"}, + {"SST-VIETNAMESE-R.OTF", "SSTVietnamese-Roman.otf"}, + {"SST-VIETNAMESE-M.OTF", "SSTVietnamese-Medium.otf"}, + {"SST-VIETNAMESE-B.OTF", "SSTVietnamese-Bold.otf"}, + }}; + + for (const auto& [from, to] : kAliases) { + if (ps4_filename == from) { + return resolve_existing(to); + } + if (ps4_filename == to) { + if (auto reverse = resolve_existing(from)) { + return reverse; + } + } + } + return std::nullopt; +} + +static std::mutex g_sysfont_file_cache_mutex; +static std::unordered_map>> + g_sysfont_file_cache; + +static std::shared_ptr> GetCachedFontBytes( + const std::filesystem::path& host_path) { + const std::string key = host_path.string(); + { + std::scoped_lock lock(g_sysfont_file_cache_mutex); + if (auto it = g_sysfont_file_cache.find(key); it != g_sysfont_file_cache.end()) { + return it->second; + } + } + + std::vector bytes; + if (!LoadGuestFileBytes(host_path, bytes) || bytes.empty()) { + return {}; + } + + auto shared = std::make_shared>(std::move(bytes)); + { + std::scoped_lock lock(g_sysfont_file_cache_mutex); + g_sysfont_file_cache.emplace(key, shared); + } + return shared; +} + +} // namespace + +std::optional ResolveKnownSysFontAlias( + const std::filesystem::path& sysfonts_dir, std::string_view ps4_filename) { + return ResolveKnownSysFontAliasImpl(sysfonts_dir, ps4_filename); +} + +FontState::~FontState() { + DestroyFreeTypeFace(ext_ft_face); + for (auto& fb : system_fallback_faces) { + DestroyFreeTypeFace(fb.ft_face); + } +} + +std::mutex g_generated_glyph_mutex; +std::unordered_set g_generated_glyphs; + +void TrackGeneratedGlyph(OrbisFontGlyph glyph) { + std::scoped_lock lock(g_generated_glyph_mutex); + g_generated_glyphs.insert(glyph); +} + +bool ForgetGeneratedGlyph(OrbisFontGlyph glyph) { + std::scoped_lock lock(g_generated_glyph_mutex); + return g_generated_glyphs.erase(glyph) > 0; +} + +GeneratedGlyph* TryGetGeneratedGlyph(OrbisFontGlyph glyph) { + if (!glyph || glyph->magic != 0x0F03) { + return nullptr; + } + std::scoped_lock lock(g_generated_glyph_mutex); + if (g_generated_glyphs.find(glyph) == g_generated_glyphs.end()) { + return nullptr; + } + return reinterpret_cast(glyph); +} + +void PopulateGlyphMetricVariants(GeneratedGlyph& gg) { + if (gg.metrics_initialized) { + return; + } + gg.metrics_horizontal.width = gg.metrics.width; + gg.metrics_horizontal.height = gg.metrics.height; + gg.metrics_horizontal.horizontal.bearing_x = gg.metrics.Horizontal.bearingX; + gg.metrics_horizontal.horizontal.bearing_y = gg.metrics.Horizontal.bearingY; + gg.metrics_horizontal.horizontal.advance = gg.metrics.Horizontal.advance; + + gg.metrics_horizontal_x.width = gg.metrics.width; + gg.metrics_horizontal_x.horizontal.bearing_x = gg.metrics.Horizontal.bearingX; + gg.metrics_horizontal_x.horizontal.advance = gg.metrics.Horizontal.advance; + + gg.metrics_horizontal_adv.horizontal.advance = gg.metrics.Horizontal.advance; + gg.origin_x = static_cast(gg.bbox_x0); + gg.origin_y = static_cast(gg.bbox_y0); + gg.metrics_initialized = true; +} + +void BuildBoundingOutline(GeneratedGlyph& gg) { + const float left = gg.metrics.Horizontal.bearingX; + const float top = gg.metrics.Horizontal.bearingY; + const float right = gg.metrics.Horizontal.bearingX + gg.metrics.width; + const float bottom = gg.metrics.Horizontal.bearingY - gg.metrics.height; + + gg.outline_points.clear(); + gg.outline_tags.clear(); + gg.outline_contours.clear(); + + gg.outline_points.push_back({left, top}); + gg.outline_points.push_back({right, top}); + gg.outline_points.push_back({right, bottom}); + gg.outline_points.push_back({left, bottom}); + + gg.outline_tags.resize(gg.outline_points.size(), 1); + gg.outline_contours.push_back(static_cast(gg.outline_points.size() - 1)); + + gg.outline.points_ptr = gg.outline_points.data(); + gg.outline.tags_ptr = gg.outline_tags.data(); + gg.outline.contour_end_idx = gg.outline_contours.data(); + gg.outline.points_cnt = static_cast(gg.outline_points.size()); + gg.outline.contours_cnt = static_cast(gg.outline_contours.size()); + gg.outline_initialized = true; +} + +bool BuildTrueOutline(GeneratedGlyph& gg) { + auto* st = TryGetState(gg.owner_handle); + if (!st) { + return false; + } + + FT_Face primary_face = nullptr; + if (!ResolveFace(*st, gg.owner_handle, primary_face)) { + return false; + } + + const auto* native = + reinterpret_cast(gg.owner_handle); + if (!native || native->magic != 0x0F02) { + return false; + } + + u32 resolved_code = gg.codepoint; + u32 resolved_font_id = 0; + FT_Face resolved_face = primary_face; + float resolved_scale_factor = 1.0f; + float range_scale = 1.0f; + s32 shift_x_units = 0; + s32 shift_y_units = 0; + + if (native->open_info.fontset_record) { + const auto* fontset_record = + static_cast(native->open_info.fontset_record); + const bool is_internal_fontset_record = + fontset_record && + fontset_record->magic == Libraries::Font::Internal::FontSetSelector::kMagic; + u32 mapped_font_id = 0; + resolved_code = ResolveSysFontCodepoint(native->open_info.fontset_record, + static_cast(gg.codepoint), + native->open_info.fontset_flags, &mapped_font_id); + if (resolved_code == 0) { + return false; + } + + resolved_font_id = mapped_font_id; + if (!is_internal_fontset_record) { + resolved_scale_factor = st->system_font_scale_factor; + } + if (st->system_requested) { + if (mapped_font_id == st->system_font_id) { + resolved_face = st->ext_ft_face; + shift_y_units = st->system_font_shift_value; + } else { + resolved_face = nullptr; + for (const auto& fb : st->system_fallback_faces) { + if (fb.ready && fb.ft_face && fb.font_id == mapped_font_id) { + resolved_face = fb.ft_face; + resolved_scale_factor = fb.scale_factor; + shift_y_units = fb.shift_value; + break; + } + } + } + } + if (!resolved_face) { + return false; + } + if (!is_internal_fontset_record) { + if (shift_y_units == 0) { + shift_y_units = GetSysFontDesc(mapped_font_id).shift_value; + } + if (const std::uint8_t* range_rec = + FindSysFontRangeRecord(mapped_font_id, resolved_code)) { + SysFontRangeRecord rec{}; + std::memcpy(&rec, range_rec, sizeof(rec)); + shift_x_units = static_cast(rec.shift_x); + shift_y_units += static_cast(rec.shift_y); + range_scale = rec.scale_mul; + if (range_scale == 0.0f) { + range_scale = 1.0f; + } + } + } + } + + if (!resolved_face) { + return false; + } + + const FT_UInt glyph_index = + FT_Get_Char_Index(resolved_face, static_cast(resolved_code)); + if (glyph_index == 0) { + return false; + } + + const float scaled_w = gg.glyph.scale_x * resolved_scale_factor * range_scale; + const float scaled_h = gg.glyph.base_scale * resolved_scale_factor * range_scale; + const auto char_w = static_cast(static_cast(scaled_w * 64.0f)); + const auto char_h = static_cast(static_cast(scaled_h * 64.0f)); + if (FT_Set_Char_Size(resolved_face, char_w, char_h, 72, 72) != 0) { + return false; + } + + if (shift_x_units != 0 || shift_y_units != 0) { + if (!resolved_face->size) { + return false; + } + const long x_scale = static_cast(resolved_face->size->metrics.x_scale); + const long y_scale = static_cast(resolved_face->size->metrics.y_scale); + + const auto round_fixed_mul = [](long fixed_16_16, s32 value) -> s32 { + const long long prod = + static_cast(fixed_16_16) * static_cast(value); + const long long sign_adj = + (static_cast(~static_cast(value)) >> 63) * -0x10000LL; + const long long base = sign_adj + prod; + long long tmp = base - 0x8000LL; + if (tmp < 0) { + tmp = base + 0x7FFFLL; + } + return static_cast(static_cast(tmp) >> 16); + }; + + FT_Vector delta{}; + delta.x = static_cast(round_fixed_mul(x_scale, shift_x_units)); + delta.y = static_cast(round_fixed_mul(y_scale, shift_y_units)); + FT_Set_Transform(resolved_face, nullptr, &delta); + } else { + FT_Set_Transform(resolved_face, nullptr, nullptr); + } + + constexpr FT_Int32 kFtLoadFlagsBase = + static_cast(FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP); + const bool loaded = (FT_Load_Glyph(resolved_face, glyph_index, kFtLoadFlagsBase) == 0); + FT_Set_Transform(resolved_face, nullptr, nullptr); + if (!loaded) { + return false; + } + + const FT_GlyphSlot slot = resolved_face->glyph; + if (!slot || slot->format != FT_GLYPH_FORMAT_OUTLINE) { + return false; + } + + const FT_Outline& outline = slot->outline; + if (outline.n_points <= 0 || outline.n_contours <= 0 || !outline.points || !outline.tags || + !outline.contours) { + return false; + } + + gg.outline_points.clear(); + gg.outline_tags.clear(); + gg.outline_contours.clear(); + gg.outline_points.reserve(static_cast(outline.n_points)); + gg.outline_tags.reserve(static_cast(outline.n_points)); + gg.outline_contours.reserve(static_cast(outline.n_contours)); + + for (int i = 0; i < outline.n_points; ++i) { + const FT_Vector& p = outline.points[i]; + gg.outline_points.push_back( + {static_cast(p.x) / 64.0f, static_cast(p.y) / 64.0f}); + const std::uint8_t tag = outline.tags[i]; + gg.outline_tags.push_back(FT_CURVE_TAG(tag) == FT_CURVE_TAG_ON ? 1 : 0); + } + for (int c = 0; c < outline.n_contours; ++c) { + gg.outline_contours.push_back(static_cast(outline.contours[c])); + } + + const bool ok = !gg.outline_points.empty() && !gg.outline_contours.empty(); + if (!ok) { + return false; + } + + gg.outline.points_ptr = gg.outline_points.data(); + gg.outline.tags_ptr = gg.outline_tags.data(); + gg.outline.contour_end_idx = gg.outline_contours.data(); + gg.outline.points_cnt = static_cast(gg.outline_points.size()); + gg.outline.contours_cnt = static_cast(gg.outline_contours.size()); + gg.outline_initialized = true; + return true; +} + +u16 ClampToU16(float value) { + if (value <= 0.0f) { + return 0; + } + const float clamped = std::min(value, static_cast(std::numeric_limits::max())); + return static_cast(std::lround(clamped)); +} + +FontState& GetState(Libraries::Font::OrbisFontHandle h) { + std::scoped_lock lock(g_state_mutex); + return g_font_state[h]; +} + +FontState* TryGetState(Libraries::Font::OrbisFontHandle h) { + if (!h) + return nullptr; + std::scoped_lock lock(g_state_mutex); + auto it = g_font_state.find(h); + if (it == g_font_state.end()) + return nullptr; + return &it->second; +} + +void RemoveState(Libraries::Font::OrbisFontHandle h) { + if (!h) { + return; + } + std::scoped_lock lock(g_state_mutex); + g_font_state.erase(h); +} + +LibraryState& GetLibState(Libraries::Font::OrbisFontLib lib) { + std::scoped_lock lock(g_state_mutex); + return g_library_state[lib]; +} + +bool AcquireLibraryLock(FontLibOpaque* lib, u32& out_prev_lock_word) { + if (!lib) { + return false; + } + + for (;;) { + const u32 lock_word = lib->lock_word; + if (lib->magic != 0x0F01) { + return false; + } + if (static_cast(lock_word) < 0) { + Libraries::Kernel::sceKernelUsleep(0x1e); + continue; + } + + std::atomic_ref ref(lib->lock_word); + u32 expected = lock_word; + if (ref.compare_exchange_weak(expected, lock_word | 0x80000000u, + std::memory_order_acq_rel)) { + out_prev_lock_word = lock_word; + return true; + } + Libraries::Kernel::sceKernelUsleep(0x1e); + } +} + +void ReleaseLibraryLock(FontLibOpaque* lib, u32 prev_lock_word) { + if (!lib) { + return; + } + lib->lock_word = prev_lock_word & 0x7fffffff; +} + +void RemoveLibState(Libraries::Font::OrbisFontLib lib) { + LibraryState state{}; + { + std::scoped_lock lock(g_state_mutex); + auto it = g_library_state.find(lib); + if (it == g_library_state.end()) { + return; + } + state = std::move(it->second); + g_library_state.erase(it); + } + + const auto free_fn = state.free_fn; + const auto alloc_ctx = state.alloc_ctx; + auto free_owned = [&](void* p) { + if (!p) { + return; + } + if (free_fn) { + const auto free_fn_guest = + reinterpret_cast(free_fn); + free_fn_guest(alloc_ctx, p); + return; + } + std::free(p); + }; + + free_owned(state.owned_device_cache); + free_owned(state.owned_external_fonts_ctx); + free_owned(state.owned_sysfonts_ctx); + free_owned(state.owned_mspace); +} + +void LogFontOpenError(s32 rc) { + switch (rc) { + case ORBIS_FONT_ERROR_INVALID_LIBRARY: + LOG_ERROR(Lib_Font, "INVALID_LIBRARY"); + break; + case ORBIS_FONT_ERROR_INVALID_PARAMETER: + LOG_ERROR(Lib_Font, "INVALID_PARAMETER"); + break; + case ORBIS_FONT_ERROR_ALLOCATION_FAILED: + LOG_ERROR(Lib_Font, "ALLOCATION_FAILED"); + break; + case ORBIS_FONT_ERROR_FS_OPEN_FAILED: + LOG_ERROR(Lib_Font, "FS_OPEN_FAILED"); + break; + case ORBIS_FONT_ERROR_NO_SUPPORT_FUNCTION: + LOG_ERROR(Lib_Font, "NO_SUPPORT_FUNCTION"); + break; + case ORBIS_FONT_ERROR_NO_SUPPORT_FORMAT: + LOG_ERROR(Lib_Font, "NO_SUPPORT_FORMAT"); + break; + case ORBIS_FONT_ERROR_NO_SUPPORT_FONTSET: + LOG_ERROR(Lib_Font, "NO_SUPPORT_FONTSET"); + break; + case ORBIS_FONT_ERROR_FONT_OPEN_MAX: + LOG_ERROR(Lib_Font, "FONT_OPEN_MAX"); + break; + default: + LOG_ERROR(Lib_Font, "FAILED"); + break; + } +} + +void LogExternalFormatSupport(u32 formats_mask) { + LOG_INFO(Lib_Font, "ExternalFormatsMask=0x{:X}", formats_mask); +} + +bool LoadFontFile(const std::filesystem::path& path, std::vector& out_bytes); + +void LogFontOpenParams(const Libraries::Font::OrbisFontOpenParams* params) { + if (!params) { + LOG_INFO(Lib_Font, "OpenFontSetParams: "); + return; + } + LOG_INFO( + Lib_Font, + "OpenFontSetParams: tag=0x{:04X} flags=0x{:X} subfont={} unique_id={} reserved_ptrs=[{}, " + "{}]", + params->tag, params->flags, params->subfont_index, params->unique_id, params->reserved_ptr1, + params->reserved_ptr2); +} + +std::filesystem::path ResolveGuestPath(const char* guest_path) { + if (!guest_path) { + return {}; + } + if (guest_path[0] != '/') { + return std::filesystem::path(guest_path); + } + if (!g_mnt) { + g_mnt = Common::Singleton::Instance(); + } + if (!g_mnt) { + return {}; + } + return g_mnt->GetHostPath(guest_path); +} + +bool LoadGuestFileBytes(const std::filesystem::path& host_path, + std::vector& out_bytes) { + std::ifstream file(host_path, std::ios::binary | std::ios::ate); + if (!file) { + return false; + } + const std::streamoff size = file.tellg(); + if (size < 0) { + return false; + } + if (size == 0) { + out_bytes.clear(); + return true; + } + if (static_cast(size) > std::numeric_limits::max()) { + return false; + } + out_bytes.resize(static_cast(size)); + file.seekg(0, std::ios::beg); + if (!file.read(reinterpret_cast(out_bytes.data()), + static_cast(out_bytes.size()))) { + out_bytes.clear(); + return false; + } + return true; +} + +FaceMetrics LoadFaceMetrics(FT_Face face) { + FaceMetrics m{}; + if (!face) { + return m; + } + m.units_per_em = static_cast(face->units_per_EM); + + if (const auto* hhea = + static_cast(FT_Get_Sfnt_Table(face, ft_sfnt_hhea))) { + m.hhea_ascent = static_cast(hhea->Ascender); + m.hhea_descent = static_cast(hhea->Descender); + m.hhea_lineGap = static_cast(hhea->Line_Gap); + } else { + m.hhea_ascent = static_cast(face->ascender); + m.hhea_descent = static_cast(face->descender); + const int height = static_cast(face->height); + m.hhea_lineGap = height - (m.hhea_ascent - m.hhea_descent); + } + + if (const auto* os2 = static_cast(FT_Get_Sfnt_Table(face, ft_sfnt_os2))) { + m.has_typo = true; + m.typo_ascent = static_cast(os2->sTypoAscender); + m.typo_descent = static_cast(os2->sTypoDescender); + m.typo_lineGap = static_cast(os2->sTypoLineGap); + m.use_typo = (os2->fsSelection & (1u << 7)) != 0; + } + return m; +} + +void PopulateStateMetrics(FontState& st, const FaceMetrics& m) { + st.ext_units_per_em = m.units_per_em; + st.ext_ascent = m.use_typo && m.has_typo ? m.typo_ascent : m.hhea_ascent; + st.ext_descent = m.use_typo && m.has_typo ? m.typo_descent : m.hhea_descent; + st.ext_lineGap = m.use_typo && m.has_typo ? m.typo_lineGap : m.hhea_lineGap; + st.ext_typo_ascent = m.typo_ascent; + st.ext_typo_descent = m.typo_descent; + st.ext_typo_lineGap = m.typo_lineGap; + st.ext_use_typo_metrics = m.use_typo && m.has_typo; +} + +float ComputeScaleExtForState(const FontState& st, FT_Face face, float pixel_h) { + if (st.ext_units_per_em > 0) { + return pixel_h / static_cast(st.ext_units_per_em); + } + if (!face || face->units_per_EM == 0) { + return 0.0f; + } + return pixel_h / static_cast(face->units_per_EM); +} + +float ComputeScaleForPixelHeight(const FontState& st, float pixel_h) { + if (st.ext_units_per_em <= 0) { + return 0.0f; + } + return pixel_h / static_cast(st.ext_units_per_em); +} + +const float kPointsPerInch = 72.0f; +const float kScaleEpsilon = 1e-4f; + +float SafeDpiToFloat(u32 dpi) { + return dpi == 0 ? kPointsPerInch : static_cast(dpi); +} + +float PointsToPixels(float pt, u32 dpi) { + return pt * SafeDpiToFloat(dpi) / kPointsPerInch; +} + +float PixelsToPoints(float px, u32 dpi) { + const float dpi_f = SafeDpiToFloat(dpi); + return px * kPointsPerInch / dpi_f; +} + +const u16 kStyleFrameMagic = 0x0F09; +const u8 kStyleFrameFlagScale = 0x01; +const u8 kStyleFrameFlagSlant = 0x02; +const u8 kStyleFrameFlagWeight = 0x04; + +StyleFrameScaleState ResolveStyleFrameScale(const OrbisFontStyleFrame* style, const FontState& st) { + StyleFrameScaleState resolved{ + .scale_w = st.scale_w, + .scale_h = st.scale_h, + .dpi_x = st.dpi_x, + .dpi_y = st.dpi_y, + .scale_overridden = false, + .dpi_overridden = false, + }; + if (!style || style->magic != kStyleFrameMagic) { + return resolved; + } + if ((style->flags1 & kStyleFrameFlagScale) != 0) { + const bool unit_is_pixel = (style->scaleUnit == 0); + const u32 dpi_x = style->hDpi; + const u32 dpi_y = style->vDpi; + resolved.scale_w = unit_is_pixel ? style->scalePixelW + : (dpi_x ? PointsToPixels(style->scalePixelW, dpi_x) + : style->scalePixelW); + resolved.scale_h = unit_is_pixel ? style->scalePixelH + : (dpi_y ? PointsToPixels(style->scalePixelH, dpi_y) + : style->scalePixelH); + resolved.scale_overridden = true; + } + if (!resolved.scale_overridden && st.scale_point_active) { + resolved.scale_w = PointsToPixels(st.scale_point_w, resolved.dpi_x); + resolved.scale_h = PointsToPixels(st.scale_point_h, resolved.dpi_y); + } + return resolved; +} + +void InitializeStyleFrame(OrbisFontStyleFrame& frame) { + std::memset(&frame, 0, sizeof(frame)); + frame.magic = kStyleFrameMagic; + frame.hDpi = 72; + frame.vDpi = 72; +} + +bool EnsureStyleFrameInitialized(OrbisFontStyleFrame* frame) { + return ValidateStyleFramePtr(frame); +} + +bool ValidateStyleFramePtr(const OrbisFontStyleFrame* frame) { + return frame && frame->magic == kStyleFrameMagic; +} + +void UpdateCachedLayout(FontState& st) { + if (!st.ext_face_ready || !st.ext_ft_face) { + return; + } + if (st.ext_scale_for_height == 0.0f) { + st.ext_scale_for_height = + ComputeScaleExtForState(st, st.ext_ft_face, st.scale_h * st.system_font_scale_factor); + } + const float scale = st.ext_scale_for_height; + st.cached_baseline_y = static_cast(st.ext_ascent) * scale; + st.layout_cached = true; +} + +std::uint64_t MakeGlyphKey(u32 code, int pixel_h) { + return (static_cast(code) << 32) | static_cast(pixel_h); +} + +void ClearRenderOutputs(Libraries::Font::OrbisFontGlyphMetrics* metrics, + Libraries::Font::OrbisFontRenderOutput* result) { + if (metrics) { + *metrics = {}; + } + if (result) { + *result = {}; + } +} + +bool ResolveFaceAndScale(FontState& st, Libraries::Font::OrbisFontHandle handle, float pixel_h, + FT_Face& face_out, float& scale_y_out) { + face_out = nullptr; + scale_y_out = 0.0f; + if (st.ext_face_ready) { + face_out = st.ext_ft_face; + } + if (!face_out) { + bool system_attached = false; + const std::string sys_log = ReportSystemFaceRequest(st, handle, system_attached); + if (!sys_log.empty()) { + LOG_ERROR(Lib_Font, "{}", sys_log); + } + if (system_attached) { + face_out = st.ext_ft_face; + } + } + if (pixel_h <= kScaleEpsilon) { + return false; + } + if (!face_out) { + return false; + } + scale_y_out = ComputeScaleExtForState(st, face_out, pixel_h * st.system_font_scale_factor); + return scale_y_out > kScaleEpsilon; +} + +bool ResolveFace(FontState& st, Libraries::Font::OrbisFontHandle handle, FT_Face& face_out) { + face_out = nullptr; + if (st.ext_face_ready) { + face_out = st.ext_ft_face; + } + if (!face_out) { + bool system_attached = false; + const std::string sys_log = ReportSystemFaceRequest(st, handle, system_attached); + if (!sys_log.empty()) { + LOG_ERROR(Lib_Font, "{}", sys_log); + } + if (system_attached) { + face_out = st.ext_ft_face; + } + } + return face_out != nullptr; +} + +s32 RenderCodepointToSurface(FontState& st, Libraries::Font::OrbisFontHandle handle, FT_Face face, + float pixel_w, float pixel_h, + Libraries::Font::OrbisFontRenderSurface* surf, u32 code, float x, + float y, Libraries::Font::OrbisFontGlyphMetrics* metrics, + Libraries::Font::OrbisFontRenderOutput* result, s32 shift_x_units, + s32 shift_y_units) { + ClearRenderOutputs(metrics, result); + + if (!surf || !metrics || !result) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + if (!surf->buffer || surf->width <= 0 || surf->height <= 0 || surf->widthByte <= 0 || + surf->pixelSizeByte <= 0) { + return ORBIS_FONT_ERROR_NO_SUPPORT_SURFACE; + } + + const int bpp = static_cast(surf->pixelSizeByte); + if (bpp != 1 && bpp != 4) { + return ORBIS_FONT_ERROR_NO_SUPPORT_SURFACE; + } + + FT_Face primary_face = face; + if (!primary_face) { + float primary_scale_y = 0.0f; + if (!ResolveFaceAndScale(st, handle, pixel_h, primary_face, primary_scale_y)) { + return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH; + } + } + + auto resolve_scale_factor = [&](FT_Face candidate) -> float { + if (!candidate) { + return st.system_font_scale_factor; + } + if (candidate == st.ext_ft_face) { + return st.system_font_scale_factor; + } + for (const auto& fb : st.system_fallback_faces) { + if (fb.ready && fb.ft_face == candidate) { + return fb.scale_factor; + } + } + return st.system_font_scale_factor; + }; + + FT_Face resolved_face = primary_face; + FT_UInt resolved_glyph_index = + resolved_face ? FT_Get_Char_Index(resolved_face, static_cast(code)) : 0; + float resolved_scale_factor = resolve_scale_factor(resolved_face); + if (resolved_glyph_index == 0) { + if (st.ext_face_ready && st.ext_ft_face && resolved_face != st.ext_ft_face) { + const FT_UInt gi = FT_Get_Char_Index(st.ext_ft_face, static_cast(code)); + if (gi != 0) { + resolved_face = st.ext_ft_face; + resolved_glyph_index = gi; + resolved_scale_factor = st.system_font_scale_factor; + } + } + if (resolved_glyph_index == 0) { + for (const auto& fb : st.system_fallback_faces) { + if (!fb.ready || !fb.ft_face) { + continue; + } + const FT_UInt gi = FT_Get_Char_Index(fb.ft_face, static_cast(code)); + if (gi == 0) { + continue; + } + resolved_face = fb.ft_face; + resolved_glyph_index = gi; + resolved_scale_factor = fb.scale_factor; + break; + } + } + } + if (!resolved_face || resolved_glyph_index == 0) { + return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH; + } + + const auto set_size = [&](float w, float h) -> bool { + const auto char_w = static_cast(static_cast(w * 64.0f)); + const auto char_h = static_cast(static_cast(h * 64.0f)); + return FT_Set_Char_Size(resolved_face, char_w, char_h, 72, 72) == 0; + }; + if (!set_size(pixel_w * resolved_scale_factor, pixel_h * resolved_scale_factor)) { + return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH; + } + + const float frac_x = x - std::floor(x); + const float frac_y = y - std::floor(y); + FT_Vector delta{}; + delta.x = static_cast(static_cast(frac_x * 64.0f)); + delta.y = static_cast(-static_cast(frac_y * 64.0f)); + + if ((shift_x_units != 0 || shift_y_units != 0) && resolved_face->size) { + const long x_scale = static_cast(resolved_face->size->metrics.x_scale); + const long y_scale = static_cast(resolved_face->size->metrics.y_scale); + + const auto round_fixed_mul = [](long fixed_16_16, s32 value) -> s32 { + const long long prod = + static_cast(fixed_16_16) * static_cast(value); + const long long sign_adj = + (static_cast(~static_cast(value)) >> 63) * -0x10000LL; + const long long base = sign_adj + prod; + long long tmp = base - 0x8000LL; + if (tmp < 0) { + tmp = base + 0x7FFFLL; + } + return static_cast(static_cast(tmp) >> 16); + }; + + delta.x += static_cast(round_fixed_mul(x_scale, shift_x_units)); + delta.y += static_cast(round_fixed_mul(y_scale, shift_y_units)); + } + FT_Set_Transform(resolved_face, nullptr, &delta); + + if (FT_Load_Glyph(resolved_face, resolved_glyph_index, kFtLoadFlagsRender) != 0) { + FT_Set_Transform(resolved_face, nullptr, nullptr); + return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH; + } + std::vector glyph_bitmap; + int x0 = 0, y0 = 0; + int glyph_w = 0; + int glyph_h = 0; + + const FT_GlyphSlot slot = resolved_face->glyph; + glyph_w = static_cast(slot->bitmap.width); + glyph_h = static_cast(slot->bitmap.rows); + x0 = static_cast(slot->bitmap_left); + y0 = -static_cast(slot->bitmap_top); + FT_Set_Transform(resolved_face, nullptr, nullptr); + + const float bearing_x = static_cast(slot->metrics.horiBearingX) / 64.0f; + const float bearing_y = static_cast(slot->metrics.horiBearingY) / 64.0f; + const float advance = static_cast(slot->metrics.horiAdvance) / 64.0f; + const float width_px = static_cast(slot->metrics.width) / 64.0f; + const float height_px = static_cast(slot->metrics.height) / 64.0f; + + metrics->width = width_px; + metrics->height = height_px; + metrics->Horizontal.bearingX = bearing_x; + metrics->Horizontal.bearingY = bearing_y; + metrics->Horizontal.advance = advance; + metrics->Vertical.bearingX = 0.0f; + metrics->Vertical.bearingY = 0.0f; + metrics->Vertical.advance = 0.0f; + + glyph_bitmap.resize(static_cast(glyph_w) * static_cast(glyph_h)); + if (glyph_w > 0 && glyph_h > 0) { + const int pitch = static_cast(slot->bitmap.pitch); + const unsigned char* src = reinterpret_cast(slot->bitmap.buffer); + if (slot->bitmap.pixel_mode == FT_PIXEL_MODE_MONO) { + for (int row = 0; row < glyph_h; ++row) { + const unsigned char* src_row = src + static_cast(row) * pitch; + unsigned char* dst_row = + glyph_bitmap.data() + static_cast(row) * glyph_w; + for (int col = 0; col < glyph_w; ++col) { + const unsigned char byte = src_row[col >> 3]; + const unsigned char bit = static_cast(0x80u >> (col & 7)); + dst_row[col] = (byte & bit) ? 0xFFu : 0x00u; + } + } + } else { + for (int row = 0; row < glyph_h; ++row) { + const unsigned char* src_row = src + static_cast(row) * pitch; + unsigned char* dst_row = + glyph_bitmap.data() + static_cast(row) * glyph_w; + std::memcpy(dst_row, src_row, static_cast(glyph_w)); + } + } + } + + const int dest_x = static_cast(std::floor(x)) + x0; + const int dest_y = static_cast(std::floor(y)) + y0; + + const int surface_w = std::max(surf->width, 0); + const int surface_h = std::max(surf->height, 0); + const int clip_x0 = std::clamp(static_cast(surf->sc_x0), 0, surface_w); + const int clip_y0 = std::clamp(static_cast(surf->sc_y0), 0, surface_h); + const int clip_x1 = std::clamp(static_cast(surf->sc_x1), 0, surface_w); + const int clip_y1 = std::clamp(static_cast(surf->sc_y1), 0, surface_h); + + int update_x0 = dest_x; + int update_y0 = dest_y; + int update_w = 0; + int update_h = 0; + + if (glyph_w > 0 && glyph_h > 0 && clip_x1 > clip_x0 && clip_y1 > clip_y0) { + auto* dst_base = static_cast(surf->buffer); + const int start_row = std::max(dest_y, clip_y0); + const int end_row = std::min(dest_y + glyph_h, clip_y1); + const int start_col = std::max(dest_x, clip_x0); + const int end_col = std::min(dest_x + glyph_w, clip_x1); + + update_x0 = start_col; + update_y0 = start_row; + update_w = std::max(0, end_col - start_col); + update_h = std::max(0, end_row - start_row); + + for (int row = start_row; row < end_row; ++row) { + const int src_y = row - dest_y; + if (src_y < 0 || src_y >= glyph_h) + continue; + const std::uint8_t* src_row = + glyph_bitmap.data() + static_cast(src_y) * glyph_w; + std::uint8_t* dst_row = dst_base + static_cast(row) * + static_cast(surf->widthByte); + for (int col = start_col; col < end_col; ++col) { + const int src_x = col - dest_x; + if (src_x < 0 || src_x >= glyph_w) + continue; + const std::uint8_t cov = src_row[src_x]; + std::uint8_t* dst = dst_row + static_cast(col) * bpp; + if (bpp == 1) { + dst[0] = cov; + } else { + dst[0] = cov; + dst[1] = cov; + dst[2] = cov; + dst[3] = cov; + } + } + } + } + + result->stage = nullptr; + result->SurfaceImage.address = static_cast(surf->buffer); + result->SurfaceImage.widthByte = static_cast(surf->widthByte); + result->SurfaceImage.pixelSizeByte = static_cast(surf->pixelSizeByte); + result->SurfaceImage.pixelFormat = 0; + result->SurfaceImage.pad16 = 0; + result->UpdateRect.x = static_cast(std::max(update_x0, 0)); + result->UpdateRect.y = static_cast(std::max(update_y0, 0)); + result->UpdateRect.w = static_cast(std::max(update_w, 0)); + result->UpdateRect.h = static_cast(std::max(update_h, 0)); + result->SurfaceImage.address = + static_cast(surf->buffer) + + static_cast(result->UpdateRect.y) * static_cast(surf->widthByte) + + static_cast(result->UpdateRect.x) * static_cast(bpp); + const auto floor_int = [](float v) -> int { + int i = static_cast(std::trunc(v)); + if (static_cast(i) > v) { + --i; + } + return i; + }; + const auto ceil_int = [](float v) -> int { + int i = static_cast(std::trunc(v)); + if (static_cast(i) < v) { + ++i; + } + return i; + }; + + const float left_f = x + metrics->Horizontal.bearingX; + const float top_f = y + metrics->Horizontal.bearingY; + const float right_f = left_f + metrics->width; + const float bottom_f = top_f - metrics->height; + + const int left_i = floor_int(left_f); + const int top_i = floor_int(top_f); + const int right_i = ceil_int(right_f); + const int bottom_i = ceil_int(bottom_f); + + const float adv_f = x + metrics->Horizontal.advance; + const float adv_snapped = static_cast(floor_int(adv_f)) - x; + + result->ImageMetrics.bearingX = static_cast(left_i) - x; + result->ImageMetrics.bearingY = static_cast(top_i) - y; + result->ImageMetrics.advance = adv_snapped; + int stride_i = right_i + 1; + const float adjust = static_cast(right_i) - right_f; + const int tmp_i = floor_int(adv_f + adjust); + const int adv_trunc_i = static_cast(std::trunc(adv_f)); + if (adv_trunc_i == 0) { + stride_i = tmp_i; + } + if (stride_i < tmp_i) { + stride_i = tmp_i; + } + result->ImageMetrics.stride = static_cast(stride_i) - x; + result->ImageMetrics.width = static_cast(std::max(0, right_i - left_i)); + result->ImageMetrics.height = static_cast(std::max(0, top_i - bottom_i)); + return ORBIS_OK; +} + +s32 RenderCodepointToSurfaceWithScale(FontState& st, Libraries::Font::OrbisFontHandle handle, + FT_Face face, float scale_x, float scale_y, + Libraries::Font::OrbisFontRenderSurface* surf, u32 code, + float x, float y, Libraries::Font::OrbisFontGlyphMetrics* m, + Libraries::Font::OrbisFontRenderOutput* result) { + ClearRenderOutputs(m, result); + + if (!surf || !m || !result) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + if (scale_x <= kScaleEpsilon || scale_y <= kScaleEpsilon) { + return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH; + } + if (!surf->buffer || surf->width <= 0 || surf->height <= 0 || surf->widthByte <= 0 || + surf->pixelSizeByte <= 0) { + return ORBIS_FONT_ERROR_NO_SUPPORT_SURFACE; + } + const int bpp = static_cast(surf->pixelSizeByte); + if (bpp != 1 && bpp != 4) { + return ORBIS_FONT_ERROR_NO_SUPPORT_SURFACE; + } + + FT_Face primary_face = face; + if (!ResolveFace(st, handle, primary_face)) { + return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH; + } + + FT_Face resolved_face = primary_face; + FT_UInt resolved_glyph_index = + resolved_face ? FT_Get_Char_Index(resolved_face, static_cast(code)) : 0; + float resolved_scale_factor = st.system_font_scale_factor; + if (resolved_glyph_index == 0) { + for (const auto& fb : st.system_fallback_faces) { + if (!fb.ready || !fb.ft_face) { + continue; + } + const FT_UInt gi = FT_Get_Char_Index(fb.ft_face, static_cast(code)); + if (gi == 0) { + continue; + } + resolved_face = fb.ft_face; + resolved_glyph_index = gi; + resolved_scale_factor = fb.scale_factor; + break; + } + } + if (!resolved_face || resolved_glyph_index == 0) { + return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH; + } + + const int base_units = (st.ext_units_per_em > 0) + ? st.ext_units_per_em + : static_cast(resolved_face->units_per_EM); + if (base_units <= 0) { + return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH; + } + + const float pixel_w = scale_x * static_cast(base_units) * resolved_scale_factor; + const float pixel_h = scale_y * static_cast(base_units) * resolved_scale_factor; + + const auto set_size = [&](float w, float h) -> bool { + const auto char_w = static_cast(static_cast(w * 64.0f)); + const auto char_h = static_cast(static_cast(h * 64.0f)); + return FT_Set_Char_Size(resolved_face, char_w, char_h, 72, 72) == 0; + }; + if (!set_size(pixel_w, pixel_h)) { + return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH; + } + + const float frac_x = x - std::floor(x); + const float frac_y = y - std::floor(y); + FT_Vector delta{}; + delta.x = static_cast(static_cast(frac_x * 64.0f)); + delta.y = static_cast(-static_cast(frac_y * 64.0f)); + FT_Set_Transform(resolved_face, nullptr, &delta); + + if (FT_Load_Glyph(resolved_face, resolved_glyph_index, kFtLoadFlagsRender) != 0) { + FT_Set_Transform(resolved_face, nullptr, nullptr); + return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH; + } + std::vector glyph_bitmap; + int x0 = 0, y0 = 0; + int glyph_w = 0; + int glyph_h = 0; + + const FT_GlyphSlot slot = resolved_face->glyph; + glyph_w = static_cast(slot->bitmap.width); + glyph_h = static_cast(slot->bitmap.rows); + x0 = static_cast(slot->bitmap_left); + y0 = -static_cast(slot->bitmap_top); + FT_Set_Transform(resolved_face, nullptr, nullptr); + + const float bearing_x = static_cast(slot->metrics.horiBearingX) / 64.0f; + const float bearing_y = static_cast(slot->metrics.horiBearingY) / 64.0f; + const float advance = static_cast(slot->metrics.horiAdvance) / 64.0f; + const float width_px = static_cast(slot->metrics.width) / 64.0f; + const float height_px = static_cast(slot->metrics.height) / 64.0f; + + m->width = width_px; + m->height = height_px; + m->Horizontal.bearingX = bearing_x; + m->Horizontal.bearingY = bearing_y; + m->Horizontal.advance = advance; + m->Vertical.bearingX = 0.0f; + m->Vertical.bearingY = 0.0f; + m->Vertical.advance = 0.0f; + + glyph_bitmap.resize(static_cast(glyph_w) * static_cast(glyph_h)); + if (glyph_w > 0 && glyph_h > 0) { + const int pitch = static_cast(slot->bitmap.pitch); + const unsigned char* src = reinterpret_cast(slot->bitmap.buffer); + if (slot->bitmap.pixel_mode == FT_PIXEL_MODE_MONO) { + for (int row = 0; row < glyph_h; ++row) { + const unsigned char* src_row = src + static_cast(row) * pitch; + unsigned char* dst_row = + glyph_bitmap.data() + static_cast(row) * glyph_w; + for (int col = 0; col < glyph_w; ++col) { + const unsigned char byte = src_row[col >> 3]; + const unsigned char bit = static_cast(0x80u >> (col & 7)); + dst_row[col] = (byte & bit) ? 0xFFu : 0x00u; + } + } + } else { + for (int row = 0; row < glyph_h; ++row) { + const unsigned char* src_row = src + static_cast(row) * pitch; + unsigned char* dst_row = + glyph_bitmap.data() + static_cast(row) * glyph_w; + std::memcpy(dst_row, src_row, static_cast(glyph_w)); + } + } + } + + const int dest_x = static_cast(std::floor(x)) + x0; + const int dest_y = static_cast(std::floor(y)) + y0; + + const int surface_w = std::max(surf->width, 0); + const int surface_h = std::max(surf->height, 0); + const int clip_x0 = std::clamp(static_cast(surf->sc_x0), 0, surface_w); + const int clip_y0 = std::clamp(static_cast(surf->sc_y0), 0, surface_h); + const int clip_x1 = std::clamp(static_cast(surf->sc_x1), 0, surface_w); + const int clip_y1 = std::clamp(static_cast(surf->sc_y1), 0, surface_h); + + int update_x0 = dest_x; + int update_y0 = dest_y; + int update_w = 0; + int update_h = 0; + + if (glyph_w > 0 && glyph_h > 0 && clip_x1 > clip_x0 && clip_y1 > clip_y0) { + auto* dst_base = static_cast(surf->buffer); + const int start_row = std::max(dest_y, clip_y0); + const int end_row = std::min(dest_y + glyph_h, clip_y1); + const int start_col = std::max(dest_x, clip_x0); + const int end_col = std::min(dest_x + glyph_w, clip_x1); + + update_x0 = start_col; + update_y0 = start_row; + update_w = std::max(0, end_col - start_col); + update_h = std::max(0, end_row - start_row); + + for (int row = start_row; row < end_row; ++row) { + const int src_y = row - dest_y; + if (src_y < 0 || src_y >= glyph_h) + continue; + const std::uint8_t* src_row = + glyph_bitmap.data() + static_cast(src_y) * glyph_w; + std::uint8_t* dst_row = dst_base + static_cast(row) * + static_cast(surf->widthByte); + for (int col = start_col; col < end_col; ++col) { + const int src_x = col - dest_x; + if (src_x < 0 || src_x >= glyph_w) + continue; + const std::uint8_t cov = src_row[src_x]; + std::uint8_t* dst = dst_row + static_cast(col) * bpp; + if (bpp == 1) { + dst[0] = cov; + } else { + dst[0] = cov; + dst[1] = cov; + dst[2] = cov; + dst[3] = cov; + } + } + } + } + + result->stage = nullptr; + result->SurfaceImage.address = static_cast(surf->buffer); + result->SurfaceImage.widthByte = static_cast(surf->widthByte); + result->SurfaceImage.pixelSizeByte = static_cast(surf->pixelSizeByte); + result->SurfaceImage.pixelFormat = 0; + result->SurfaceImage.pad16 = 0; + result->UpdateRect.x = static_cast(std::max(update_x0, 0)); + result->UpdateRect.y = static_cast(std::max(update_y0, 0)); + result->UpdateRect.w = static_cast(std::max(update_w, 0)); + result->UpdateRect.h = static_cast(std::max(update_h, 0)); + result->SurfaceImage.address = + static_cast(surf->buffer) + + static_cast(result->UpdateRect.y) * static_cast(surf->widthByte) + + static_cast(result->UpdateRect.x) * static_cast(bpp); + const auto floor_int = [](float v) -> int { + int i = static_cast(std::trunc(v)); + if (static_cast(i) > v) { + --i; + } + return i; + }; + const auto ceil_int = [](float v) -> int { + int i = static_cast(std::trunc(v)); + if (static_cast(i) < v) { + ++i; + } + return i; + }; + + const float left_f = x + m->Horizontal.bearingX; + const float top_f = y + m->Horizontal.bearingY; + const float right_f = left_f + m->width; + const float bottom_f = top_f - m->height; + + const int left_i = floor_int(left_f); + const int top_i = floor_int(top_f); + const int right_i = ceil_int(right_f); + const int bottom_i = ceil_int(bottom_f); + + const float adv_f = x + m->Horizontal.advance; + const float adv_snapped = static_cast(floor_int(adv_f)) - x; + + result->ImageMetrics.bearingX = static_cast(left_i) - x; + result->ImageMetrics.bearingY = static_cast(top_i) - y; + result->ImageMetrics.advance = adv_snapped; + int stride_i = right_i + 1; + const float adjust = static_cast(right_i) - right_f; + const int tmp_i = floor_int(adv_f + adjust); + const int adv_trunc_i = static_cast(std::trunc(adv_f)); + if (adv_trunc_i == 0) { + stride_i = tmp_i; + } + if (stride_i < tmp_i) { + stride_i = tmp_i; + } + result->ImageMetrics.stride = static_cast(stride_i) - x; + result->ImageMetrics.width = static_cast(std::max(0, right_i - left_i)); + result->ImageMetrics.height = static_cast(std::max(0, top_i - bottom_i)); + return ORBIS_OK; +} + +const GlyphEntry* GetGlyphEntry(FontState& st, Libraries::Font::OrbisFontHandle handle, u32 code, + FT_Face& face_out, float& scale_out) { + face_out = nullptr; + scale_out = 0.0f; + + if (st.ext_face_ready) { + face_out = st.ext_ft_face; + } + if (!face_out) { + bool system_attached = false; + const std::string sys_log = ReportSystemFaceRequest(st, handle, system_attached); + if (!sys_log.empty()) { + LOG_ERROR(Lib_Font, "SYSTEM_FONT_FAILED"); + LOG_DEBUG(Lib_Font, "{}", sys_log); + } + if (system_attached) { + face_out = st.ext_ft_face; + } + } + if (!face_out) { + return nullptr; + } + + float resolved_scale_factor = st.system_font_scale_factor; + FT_Face resolved_face = face_out; + FT_UInt resolved_glyph_index = FT_Get_Char_Index(resolved_face, static_cast(code)); + if (resolved_glyph_index == 0) { + for (const auto& fb : st.system_fallback_faces) { + if (!fb.ready || !fb.ft_face) { + continue; + } + const FT_UInt gi = FT_Get_Char_Index(fb.ft_face, static_cast(code)); + if (gi == 0) { + continue; + } + resolved_face = fb.ft_face; + resolved_glyph_index = gi; + resolved_scale_factor = fb.scale_factor; + break; + } + } + if (!resolved_face || resolved_glyph_index == 0) { + return nullptr; + } + + const float pixel_h_f = st.scale_h * resolved_scale_factor; + const int pixel_h = std::max(1, static_cast(std::lround(pixel_h_f))); + const std::uint64_t key = MakeGlyphKey(code, pixel_h); + + GlyphEntry* ge = nullptr; + if (auto it = st.ext_cache.find(key); it != st.ext_cache.end()) { + ge = &it->second; + } + if (!ge) { + const auto set_size = [&](float h) -> bool { + const auto char_h = static_cast(static_cast(h * 64.0f)); + return FT_Set_Char_Size(resolved_face, 0, char_h, 72, 72) == 0; + }; + if (!set_size(pixel_h_f)) { + return nullptr; + } + + if (FT_Load_Glyph(resolved_face, resolved_glyph_index, kFtLoadFlagsRender) != 0) { + return nullptr; + } + + const FT_GlyphSlot slot = resolved_face->glyph; + GlyphEntry entry{}; + entry.w = static_cast(slot->bitmap.width); + entry.h = static_cast(slot->bitmap.rows); + entry.x0 = static_cast(slot->bitmap_left); + entry.y0 = -static_cast(slot->bitmap_top); + entry.x1 = entry.x0 + entry.w; + entry.y1 = entry.y0 + entry.h; + entry.advance = static_cast(slot->metrics.horiAdvance) / 64.0f; + entry.bearingX = static_cast(slot->bitmap_left); + ge = &st.ext_cache.emplace(key, std::move(entry)).first->second; + } + + face_out = resolved_face; + scale_out = ComputeScaleExtForState(st, resolved_face, pixel_h_f); + return ge; +} + +constexpr SystemFontDefinition kSystemFontDefinitions[] = { + {0x18070043, "FONTSET_SST_STD_EUROPEAN_LIGHT", "SST-Light.otf"}, + {0x18070044, "FONTSET_SST_STD_EUROPEAN", "SST-Roman.otf"}, + {0x18070045, "FONTSET_SST_STD_EUROPEAN_MEDIUM", "SST-Medium.otf"}, + {0x18070047, "FONTSET_SST_STD_EUROPEAN_BOLD", "SST-Bold.otf"}, + {0x18070053, "FONTSET_SST_STD_VIETNAMESE_LIGHT", "SSTVietnamese-Light.otf"}, + {0x18070054, "FONTSET_SST_STD_VIETNAMESE", "SSTVietnamese-Roman.otf"}, + {0x18070055, "FONTSET_SST_STD_VIETNAMESE_MEDIUM", "SSTVietnamese-Medium.otf"}, + {0x18070057, "FONTSET_SST_STD_VIETNAMESE_BOLD", "SSTVietnamese-Bold.otf"}, + {0x180700C3, "FONTSET_SST_STD_EUROPEAN_AR_LIGHT", "SST-Light.otf"}, + {0x180700C4, "FONTSET_SST_STD_EUROPEAN_AR", "SST-Roman.otf"}, + {0x180700C5, "FONTSET_SST_STD_EUROPEAN_AR_MEDIUM", "SST-Medium.otf"}, + {0x180700C7, "FONTSET_SST_STD_EUROPEAN_AR_BOLD", "SST-Bold.otf"}, + {0x18070444, "FONTSET_SST_STD_EUROPEAN_JP", "SSTJpPro-Regular.otf"}, + {0x18070447, "FONTSET_SST_STD_EUROPEAN_JP_BOLD", "SSTJpPro-Bold.otf"}, + {0x18070454, "FONTSET_SST_STD_EUROPEAN_JP", "SSTJpPro-Regular.otf"}, + {0x18070457, "FONTSET_SST_STD_EUROPEAN_JP_BOLD", "SSTJpPro-Bold.otf"}, + {0x180704C4, "FONTSET_SST_STD_EUROPEAN_JP_AR", "SSTJpPro-Regular.otf"}, + {0x180704C7, "FONTSET_SST_STD_EUROPEAN_JP_AR_BOLD", "SSTJpPro-Bold.otf"}, + {0x18071053, "FONTSET_SST_STD_THAI_LIGHT", "SSTVietnamese-Light.otf"}, + {0x18071054, "FONTSET_SST_STD_THAI", "SSTVietnamese-Roman.otf"}, + {0x18071055, "FONTSET_SST_STD_THAI_MEDIUM", "SSTVietnamese-Medium.otf"}, + {0x18071057, "FONTSET_SST_STD_THAI_BOLD", "SSTVietnamese-Bold.otf"}, + {0x18071454, "FONTSET_SST_STD_EUROPEAN_JP_TH", "SSTJpPro-Regular.otf"}, + {0x18071457, "FONTSET_SST_STD_EUROPEAN_JP_TH_BOLD", "SSTJpPro-Bold.otf"}, + {0x18072444, "FONTSET_SST_STD_EUROPEAN_JPUH", "SSTJpPro-Regular.otf"}, + {0x18072447, "FONTSET_SST_STD_EUROPEAN_JPUH_BOLD", "SSTJpPro-Bold.otf"}, + {0x180724C4, "FONTSET_SST_STD_EUROPEAN_JPUH_AR", "SSTJpPro-Regular.otf"}, + {0x180724C7, "FONTSET_SST_STD_EUROPEAN_JPUH_AR_BOLD", "SSTJpPro-Bold.otf"}, + {0x18073454, "FONTSET_SST_STD_EUROPEAN_JPUH_TH", "SSTJpPro-Regular.otf"}, + {0x18073457, "FONTSET_SST_STD_EUROPEAN_JPUH_TH_BOLD", "SSTJpPro-Bold.otf"}, + {0x180734D4, "FONTSET_SST_STD_EUROPEAN_JPUH_TH_AR", "SSTJpPro-Regular.otf"}, + {0x180734D7, "FONTSET_SST_STD_EUROPEAN_JPUH_TH_AR_BOLD", "SSTJpPro-Bold.otf"}, + {0x18078044, "FONTSET_SST_STD_EUROPEAN_GB", "DFHEI5-SONY.ttf"}, + {0x180780C4, "FONTSET_SST_STD_EUROPEAN_GB_AR", "DFHEI5-SONY.ttf"}, + {0x18079054, "FONTSET_SST_STD_EUROPEAN_GB_TH", "DFHEI5-SONY.ttf"}, + {0x1807A044, "FONTSET_SST_STD_EUROPEAN_GBUH", "DFHEI5-SONY.ttf"}, + {0x1807A0C4, "FONTSET_SST_STD_EUROPEAN_GBUH_AR", "DFHEI5-SONY.ttf"}, + {0x1807A444, "FONTSET_SST_STD_EUROPEAN_JPCJK", "SSTJpPro-Regular.otf"}, + {0x1807A4C4, "FONTSET_SST_STD_EUROPEAN_JPCJK_AR", "SSTJpPro-Regular.otf"}, + {0x1807AC44, "FONTSET_SST_STD_EUROPEAN_GBCJK", "SCEPS4Yoongd-Medium.otf"}, + {0x1807ACC4, "FONTSET_SST_STD_EUROPEAN_GBCJK_AR", "DFHEI5-SONY.ttf"}, + {0x1807B054, "FONTSET_SST_STD_EUROPEAN_GBUH_TH", "DFHEI5-SONY.ttf"}, + {0x1807B0D4, "FONTSET_SST_STD_EUROPEAN_GBUH_TH_AR", "DFHEI5-SONY.ttf"}, + {0x1807B454, "FONTSET_SST_STD_EUROPEAN_JPCJK_TH", "SSTJpPro-Regular.otf"}, + {0x1807B4D4, "FONTSET_SST_STD_EUROPEAN_JPCJK_TH_AR", "SSTJpPro-Regular.otf"}, + {0x1807BC54, "FONTSET_SST_STD_EUROPEAN_GBCJK_TH", "DFHEI5-SONY.ttf"}, + {0x1807BCD4, "FONTSET_SST_STD_EUROPEAN_GBCJK_TH_AR", "DFHEI5-SONY.ttf"}, + {0x18080444, "FONTSET_SST_STD_JAPANESE_JP", "SSTJpPro-Regular.otf"}, + {0x18080447, "FONTSET_SST_STD_JAPANESE_JP_BOLD", "SSTJpPro-Bold.otf"}, + {0x18080454, "FONTSET_SST_STD_VIETNAMESE_JP", "SSTJpPro-Regular.otf"}, + {0x18080457, "FONTSET_SST_STD_VIETNAMESE_JP_BOLD", "SSTJpPro-Bold.otf"}, + {0x180804C4, "FONTSET_SST_STD_JAPANESE_JP_AR", "SSTJpPro-Regular.otf"}, + {0x180804C7, "FONTSET_SST_STD_JAPANESE_JP_AR_BOLD", "SSTJpPro-Bold.otf"}, + {0x18081454, "FONTSET_SST_STD_ASIAN_JP_TH", "SSTJpPro-Regular.otf"}, + {0x18081457, "FONTSET_SST_STD_ASIAN_JP_TH_BOLD", "SSTJpPro-Bold.otf"}, + {0x18082444, "FONTSET_SST_STD_JAPANESE_JPUH", "SSTJpPro-Regular.otf"}, + {0x18082447, "FONTSET_SST_STD_JAPANESE_JPUH_BOLD", "SSTJpPro-Bold.otf"}, + {0x180824C4, "FONTSET_SST_STD_JAPANESE_JPUH_AR", "SSTJpPro-Regular.otf"}, + {0x180824C7, "FONTSET_SST_STD_JAPANESE_JPUH_AR_BOLD", "SSTJpPro-Bold.otf"}, + {0x18083454, "FONTSET_SST_STD_ASIAN_JPUH_TH", "SSTJpPro-Regular.otf"}, + {0x18083457, "FONTSET_SST_STD_ASIAN_JPUH_TH_BOLD", "SSTJpPro-Bold.otf"}, + {0x180834D4, "FONTSET_SST_STD_ASIAN_JPUH_TH_AR", "SSTJpPro-Regular.otf"}, + {0x180834D7, "FONTSET_SST_STD_ASIAN_JPUH_TH_AR_BOLD", "SSTJpPro-Bold.otf"}, + {0x1808A444, "FONTSET_SST_STD_JAPANESE_JPCJK", "SSTJpPro-Regular.otf"}, + {0x1808A4C4, "FONTSET_SST_STD_JAPANESE_JPCJK_AR", "SSTJpPro-Regular.otf"}, + {0x1808B454, "FONTSET_SST_STD_ASIAN_JPCJK_TH", "SSTJpPro-Regular.otf"}, + {0x1808B4D4, "FONTSET_SST_STD_ASIAN_JPCJK_TH_AR", "SSTJpPro-Regular.otf"}, + {0x180C8044, "FONTSET_SST_STD_SCHINESE_GB", "DFHEI5-SONY.ttf"}, + {0x180C80C4, "FONTSET_SST_STD_SCHINESE_GB_AR", "DFHEI5-SONY.ttf"}, + {0x180C9054, "FONTSET_SST_STD_ASIAN_GB_TH", "DFHEI5-SONY.ttf"}, + {0x180CA044, "FONTSET_SST_STD_SCHINESE_GBUH", "DFHEI5-SONY.ttf"}, + {0x180CA0C4, "FONTSET_SST_STD_SCHINESE_GBUH_AR", "DFHEI5-SONY.ttf"}, + {0x180CAC44, "FONTSET_SST_STD_SCHINESE_GBCJK", "DFHEI5-SONY.ttf"}, + {0x180CACC4, "FONTSET_SST_STD_SCHINESE_GBCJK_AR", "DFHEI5-SONY.ttf"}, + {0x180CB054, "FONTSET_SST_STD_ASIAN_GBUH_TH", "DFHEI5-SONY.ttf"}, + {0x180CB0D4, "FONTSET_SST_STD_ASIAN_GBUH_TH_AR", "DFHEI5-SONY.ttf"}, + {0x180CBC54, "FONTSET_SST_STD_ASIAN_GBCJK_TH", "DFHEI5-SONY.ttf"}, + {0x180CBCD4, "FONTSET_SST_STD_ASIAN_GBCJK_TH_AR", "DFHEI5-SONY.ttf"}, + {0x18170043, "FONTSET_SST_STD_EUROPEAN_LIGHT_ITALIC", "SST-LightItalic.otf"}, + {0x18170044, "FONTSET_SST_STD_EUROPEAN_ITALIC", "SST-Italic.otf"}, + {0x18170045, "FONTSET_SST_STD_EUROPEAN_MEDIUM_ITALIC", "SST-MediumItalic.otf"}, + {0x18170047, "FONTSET_SST_STD_EUROPEAN_BOLD_ITALIC", "SST-BoldItalic.otf"}, + {0x18170444, "FONTSET_SST_STD_EUROPEAN_JP_ITALIC", "SSTJpPro-Regular.otf"}, + {0x18170447, "FONTSET_SST_STD_EUROPEAN_JP_BOLD_ITALIC", "SSTJpPro-Bold.otf"}, + {0x18370044, "FONTSET_SST_TYPEWRITER_EUROPEAN", "SSTTypewriter-Roman.otf"}, + {0x18370047, "FONTSET_SST_TYPEWRITER_EUROPEAN_BOLD", "SSTTypewriter-Bd.otf"}, + {0x18370444, "FONTSET_SST_TYPEWRITER_EUROPEAN_JP", "SSTJpPro-Regular.otf"}, + {0x18370447, "FONTSET_SST_TYPEWRITER_EUROPEAN_JP_BOLD", "SSTJpPro-Bold.otf"}, +}; + +std::mutex g_fontset_cache_mutex; +std::unordered_map g_fontset_cache; + +const SystemFontDefinition* FindSystemFontDefinition(u32 font_set_type) { + for (const auto& def : kSystemFontDefinitions) { + if (def.font_set_type == font_set_type) { + return &def; + } + } + return nullptr; +} + +static bool DirectoryContainsAnyFontFiles(const std::filesystem::path& dir) { + std::error_code ec; + if (!std::filesystem::is_directory(dir, ec)) { + return false; + } + for (const auto& entry : std::filesystem::directory_iterator(dir, ec)) { + if (ec) { + return false; + } + if (!entry.is_regular_file(ec) || ec) { + continue; + } + const auto ext = entry.path().extension().string(); + std::string lower; + lower.reserve(ext.size()); + for (const char c : ext) { + lower.push_back(static_cast(std::tolower(static_cast(c)))); + } + if (lower == ".otf" || lower == ".ttf" || lower == ".ttc" || lower == ".otc") { + return true; + } + } + return false; +} + +std::filesystem::path GetSysFontBaseDir() { + std::filesystem::path base = Config::getFontsPath(); + std::error_code ec; + if (base.empty()) { + LOG_ERROR(Lib_Font, "SystemFonts: FontsPath not set"); + return {}; + } + if (std::filesystem::is_directory(base, ec)) { + const auto font_dir = base / "font"; + const auto font2_dir = base / "font2"; + if (DirectoryContainsAnyFontFiles(base) || DirectoryContainsAnyFontFiles(font_dir) || + DirectoryContainsAnyFontFiles(font2_dir)) { + return base; + } + LOG_ERROR( + Lib_Font, + "SystemFonts: FontsPath '{}' contains no font files; expected files directly in this " + "directory or under 'font'/'font2'", + base.string()); + return {}; + } + if (std::filesystem::is_regular_file(base, ec)) { + return base.parent_path(); + } + LOG_ERROR(Lib_Font, "SystemFonts: FontsPath '{}' is not a valid directory or file", + base.string()); + return {}; +} + +static std::filesystem::path ResolveSystemFontPathCandidate(const std::filesystem::path& base_dir, + const std::filesystem::path& filename) { + if (base_dir.empty() || filename.empty()) { + return {}; + } + std::error_code ec; + const auto is_file = [&](const std::filesystem::path& p) -> bool { + return std::filesystem::is_regular_file(p, ec) && !ec; + }; + + const auto direct = base_dir / filename; + if (is_file(direct)) { + return direct; + } + + const auto base_name = base_dir.filename().string(); + if (base_name != "font" && base_name != "font2") { + const auto in_font = base_dir / "font" / filename; + if (is_file(in_font)) { + return in_font; + } + const auto in_font2 = base_dir / "font2" / filename; + if (is_file(in_font2)) { + return in_font2; + } + } + + if (base_name == "font" || base_name == "font2") { + const auto container = base_dir.parent_path(); + const auto sibling = container / ((base_name == "font") ? "font2" : "font") / filename; + if (is_file(sibling)) { + return sibling; + } + } + + return direct; +} + +std::filesystem::path ResolveSystemFontPath(u32 font_set_type) { + if (const auto* def = FindSystemFontDefinition(font_set_type); def) { + const auto base_dir = GetSysFontBaseDir(); + if (base_dir.empty()) { + return {}; + } + if (def->default_file && *def->default_file) { + return ResolveSystemFontPathCandidate(base_dir, def->default_file); + } + } + LOG_ERROR(Lib_Font, "SystemFonts: unknown font set type=0x{:08X}", font_set_type); + return {}; +} + +const FontSetCache* EnsureFontSetCache(u32 font_set_type) { + if (font_set_type == 0) { + return nullptr; + } + std::scoped_lock lock(g_fontset_cache_mutex); + auto& cache = g_fontset_cache[font_set_type]; + if (!cache.loaded) { + cache.loaded = true; + const auto path = ResolveSystemFontPath(font_set_type); + if (!path.empty() && LoadFontFile(path, cache.bytes)) { + cache.available = HasSfntTables(cache.bytes); + cache.path = cache.available ? path : std::filesystem::path{}; + } else { + cache.available = false; + LOG_ERROR(Lib_Font, "SystemFonts: failed to load font for set type=0x{:08X} path='{}'", + font_set_type, path.string()); + } + } + return cache.available ? &cache : nullptr; +} + +bool HasSfntTables(const std::vector& bytes) { + if (bytes.size() < 12) { + return false; + } + const u8* ptr = bytes.data(); + const u32 num_tables = (ptr[4] << 8) | ptr[5]; + constexpr u32 kDirEntrySize = 16; + if (bytes.size() < 12 + static_cast(num_tables) * kDirEntrySize) { + return false; + } + return true; +} + +bool LoadFontFile(const std::filesystem::path& path, std::vector& out_bytes) { + if (path.empty()) { + return false; + } + + auto try_load = [&](const std::filesystem::path& p) -> bool { + out_bytes.clear(); + if (!LoadGuestFileBytes(p, out_bytes) || out_bytes.empty()) { + out_bytes.clear(); + return false; + } + return true; + }; + + if (try_load(path)) { + return true; + } + + std::error_code ec; + const auto parent = path.parent_path(); + const auto parent_name = parent.filename().string(); + const auto file_name = path.filename(); + if (!file_name.empty() && parent_name != "font" && parent_name != "font2") { + const auto cand_font = parent / "font" / file_name; + if (std::filesystem::is_regular_file(cand_font, ec) && !ec) { + if (try_load(cand_font)) { + return true; + } + } + const auto cand_font2 = parent / "font2" / file_name; + if (std::filesystem::is_regular_file(cand_font2, ec) && !ec) { + if (try_load(cand_font2)) { + return true; + } + } + } + + if (!file_name.empty() && (parent_name == "font" || parent_name == "font2")) { + const auto container = parent.parent_path(); + const auto sibling = container / ((parent_name == "font") ? "font2" : "font") / file_name; + if (std::filesystem::is_regular_file(sibling, ec) && !ec) { + return try_load(sibling); + } + } + + return false; +} + +bool AttachSystemFont(FontState& st, Libraries::Font::OrbisFontHandle handle) { + if (st.ext_face_ready) { + return true; + } + st.system_fallback_faces.clear(); + st.system_font_id = 0; + st.system_font_scale_factor = 1.0f; + st.system_font_shift_value = 0; + + const auto* native = reinterpret_cast(handle); + const u32 subfont_index = native ? native->open_info.sub_font_index : 0u; + auto* lib = static_cast(st.library); + if (!lib || lib->magic != 0x0F01 || st.font_set_type == 0) { + return false; + } + + std::filesystem::path primary_path = ResolveSystemFontPath(st.font_set_type); + std::vector primary_bytes; + if (primary_path.empty() || !LoadFontFile(primary_path, primary_bytes)) { + return false; + } + + DestroyFreeTypeFace(st.ext_ft_face); + st.ext_face_data = std::move(primary_bytes); + st.ext_ft_face = CreateFreeTypeFaceFromBytes(st.ext_face_data.data(), st.ext_face_data.size(), + subfont_index); + if (!st.ext_ft_face) { + st.ext_face_data.clear(); + return false; + } + + st.ext_cache.clear(); + st.scratch.clear(); + st.logged_ext_use = false; + st.ext_scale_for_height = 0.0f; + st.layout_cached = false; + const FaceMetrics m = LoadFaceMetrics(st.ext_ft_face); + PopulateStateMetrics(st, m); + st.ext_face_ready = true; + st.system_font_path = primary_path; + st.system_requested = true; + + LOG_INFO(Lib_Font, "system font attached"); + LOG_DEBUG(Lib_Font, + "system font attach params:\n" + " handle={}\n" + " font_set_type=0x{:08X}\n" + " path={}\n", + static_cast(handle), st.font_set_type, st.system_font_path.string()); + return true; +} + +std::string ReportSystemFaceRequest(FontState& st, Libraries::Font::OrbisFontHandle handle, + bool& attached_out) { + attached_out = AttachSystemFont(st, handle); + if (attached_out) { + st.system_requested = true; + return {}; + } + if (!st.system_requested) { + st.system_requested = true; + const auto configured = Config::getFontsPath(); + return fmt::format("SystemFace: handle={} requested internal font but fontsPath ('{}') " + "could not be loaded", + static_cast(handle), configured.string()); + } + return {}; +} + +void LogCachedStyleOnce(Libraries::Font::OrbisFontHandle handle, + const Libraries::Font::FontHandleOpaqueNative& font) { + static std::mutex s_mutex; + static std::unordered_set s_logged; + std::scoped_lock lock(s_mutex); + if (s_logged.find(handle) != s_logged.end()) { + return; + } + s_logged.insert(handle); + + LOG_DEBUG(Lib_Font, + "BindRenderer: cached_style snapshot: hDpi={} vDpi={} scaleUnit={} baseScale={} " + "scalePixelW={} scalePixelH={} effectWeightX={} effectWeightY={} slantRatio={}", + font.cached_style.hDpi, font.cached_style.vDpi, font.cached_style.scaleUnit, + font.cached_style.baseScale, font.cached_style.scalePixelW, + font.cached_style.scalePixelH, font.cached_style.effectWeightX, + font.cached_style.effectWeightY, font.cached_style.slantRatio); +} + +void LogRenderResultSample(Libraries::Font::OrbisFontHandle handle, u32 code, + const Libraries::Font::OrbisFontGlyphMetrics& metrics, + const Libraries::Font::OrbisFontRenderOutput& result) { + static std::mutex s_mutex; + static std::unordered_map s_counts; + std::scoped_lock lock(s_mutex); + int& count = s_counts[handle]; + if (count >= 5) { + return; + } + ++count; + LOG_DEBUG(Lib_Font, + "RenderSample: handle={} code=U+{:04X} update=[{},{} {}x{}] img=[bx={} by={} adv={} " + "stride={} w={} h={}] metrics=[w={} h={} hbx={} hby={} hadv={}]", + static_cast(handle), code, result.UpdateRect.x, result.UpdateRect.y, + result.UpdateRect.w, result.UpdateRect.h, result.ImageMetrics.bearingX, + result.ImageMetrics.bearingY, result.ImageMetrics.advance, result.ImageMetrics.stride, + result.ImageMetrics.width, result.ImageMetrics.height, metrics.width, metrics.height, + metrics.Horizontal.bearingX, metrics.Horizontal.bearingY, metrics.Horizontal.advance); +} + +u8 CachedStyleCacheFlags(const Libraries::Font::OrbisFontStyleFrame& cached_style) { + return static_cast(cached_style.cache_flags_and_direction & 0xFFu); +} + +void CachedStyleSetCacheFlags(Libraries::Font::OrbisFontStyleFrame& cached_style, u8 flags) { + cached_style.cache_flags_and_direction = + (cached_style.cache_flags_and_direction & 0xFFFFFF00u) | static_cast(flags); +} + +void CachedStyleSetDirectionWord(Libraries::Font::OrbisFontStyleFrame& cached_style, u16 word) { + cached_style.cache_flags_and_direction = + (cached_style.cache_flags_and_direction & 0x0000FFFFu) | (static_cast(word) << 16); +} + +float CachedStyleGetScalar(const Libraries::Font::OrbisFontStyleFrame& cached_style) { + float value = 0.0f; + std::memcpy(&value, &cached_style.cached_scalar_bits, sizeof(value)); + return value; +} + +void CachedStyleSetScalar(Libraries::Font::OrbisFontStyleFrame& cached_style, float value) { + std::memcpy(&cached_style.cached_scalar_bits, &value, sizeof(value)); +} +} // namespace Libraries::Font::Internal diff --git a/src/core/libraries/font/font_internal.h b/src/core/libraries/font/font_internal.h new file mode 100644 index 000000000..c72e21333 --- /dev/null +++ b/src/core/libraries/font/font_internal.h @@ -0,0 +1,892 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include FT_FREETYPE_H + +#include "common/config.h" +#include "common/logging/log.h" +#include "common/singleton.h" +#include "core/file_sys/fs.h" +#include "core/libraries/error_codes.h" +#include "core/libraries/font/font.h" +#include "core/libraries/libs.h" +#include "font_error.h" + +namespace Libraries::Font { +struct FontHandleOpaqueNative; +} + +namespace Libraries::Font::Internal { + +struct FontLibOpaque; +struct OrbisFontRenderer_; +struct FontSetCache; + +template +std::string DescribeValue(const T& value) { + using Clean = std::remove_cvref_t; + if constexpr (std::is_same_v) { + return value ? "true" : "false"; + } else if constexpr (std::is_pointer_v) { + return fmt::format("{}", reinterpret_cast(value)); + } else if constexpr (std::is_floating_point_v) { + return fmt::format("{:.6g}", value); + } else if constexpr (std::is_enum_v) { + using Underlying = std::underlying_type_t; + return DescribeValue(static_cast(value)); + } else if constexpr (std::is_same_v || + std::is_same_v) { + return fmt::format("\"{}\"", value); + } else { + return fmt::format("{}", value); + } +} + +struct NamedParam { + std::string_view name; + std::string value; +}; + +template +NamedParam Param(std::string_view name, const T& value) { + return NamedParam{name, DescribeValue(value)}; +} + +std::string FormatNamedParams(std::initializer_list params); + +struct ParamRecord { + std::string name; + std::string initial; + std::string current; +}; + +template +struct ChangeTracked { + T before; + T after; +}; + +template +struct is_change_tracked : std::false_type {}; + +template +struct is_change_tracked> : std::true_type { + using value_type = T; +}; + +template +ChangeTracked trackChange(T before, T after) { + return ChangeTracked{before, after}; +} + +inline std::string TrimName(std::string_view name) { + const auto start = name.find_first_not_of(" \t\n\r"); + if (start == std::string_view::npos) { + return {}; + } + const auto end = name.find_last_not_of(" \t\n\r"); + return std::string{name.substr(start, end - start + 1)}; +} + +inline std::vector SplitArgumentNames(std::string_view names) { + std::vector result; + std::string current; + int depth = 0; + for (char c : names) { + if (c == ',' && depth == 0) { + result.push_back(TrimName(current)); + current.clear(); + continue; + } + if (c == '(' || c == '{' || c == '[') { + ++depth; + } else if (c == ')' || c == '}' || c == ']') { + depth = std::max(0, depth - 1); + } + current.push_back(c); + } + if (!current.empty() || + (!names.empty() && names.find_first_not_of(" \t\n\r") != std::string_view::npos)) { + result.push_back(TrimName(current)); + } + return result; +} + +template +ParamRecord MakeParamRecord(std::string name, T&& value) { + using Clean = std::remove_cvref_t; + if constexpr (is_change_tracked::value) { + using ValueType = typename Clean::value_type; + return ParamRecord{std::move(name), DescribeValue(value.before), + DescribeValue(value.after)}; + } else { + const std::string rendered = DescribeValue(value); + return ParamRecord{std::move(name), rendered, rendered}; + } +} + +inline std::string FormatParamRecords(const std::vector& records) { + fmt::memory_buffer buffer; + fmt::format_to(std::back_inserter(buffer), "params:\n"); + for (const auto& entry : records) { + const bool changed = entry.initial != entry.current; + if (changed) { + fmt::format_to(std::back_inserter(buffer), "{}: {} -> {}\n", entry.name, entry.initial, + entry.current); + } else { + fmt::format_to(std::back_inserter(buffer), "{}: {}\n", entry.name, entry.initial); + } + } + return fmt::to_string(buffer); +} + +template +std::string formatParamsImpl(const char* arg_names, Args&&... args) { + std::vector names = SplitArgumentNames(arg_names ? arg_names : ""); + std::vector records; + records.reserve(sizeof...(Args)); + std::size_t index = 0; + auto append_record = [&](auto&& value) { + std::string name = (index < names.size() && !names[index].empty()) + ? std::move(names[index]) + : fmt::format("arg{}", index); + ++index; + records.push_back(MakeParamRecord(std::move(name), std::forward(value))); + }; + (append_record(std::forward(args)), ...); + return FormatParamRecords(records); +} + +inline void log_info(std::string_view message) { + LOG_INFO(Lib_Font, "{}", message); +} + +inline void log_debug(std::string_view message) { + LOG_DEBUG(Lib_Font, "{}", message); +} + +#define formatParams(...) formatParamsImpl(#__VA_ARGS__ __VA_OPT__(, ) __VA_ARGS__) + +struct FontSetSelector; + +struct GlyphEntry { + std::vector bitmap; + int w = 0; + int h = 0; + int x0 = 0; + int y0 = 0; + int x1 = 0; + int y1 = 0; + float advance = 0.0f; + float bearingX = 0.0f; +}; + +struct FontState { + // `scale_*` fields are controlled by style-state setters and are interpreted according to the + // style frame `scaleUnit` (pixel vs point). Public APIs accept codepoints as UTF-32 scalar + // values; glyph lookup and rendering uses FreeType + deterministic fallback selection. + // + // Fallback behavior (high level): + // - A font handle may have an external face (opened from file/memory) and, optionally, a + // sysfont selection (font_set_type + primary sysfont + additional fallback sysfonts). + // - When a glyph is missing from the primary face, the implementation may consult the external + // face and then the configured system fallback faces to preserve observable behavior. + float scale_w = 16.0f; + float scale_h = 16.0f; + float scale_point_w = 12.0f; + float scale_point_h = 12.0f; + bool scale_point_active = false; + float render_scale_w = 16.0f; + float render_scale_h = 16.0f; + float render_scale_point_w = 12.0f; + float render_scale_point_h = 12.0f; + bool render_scale_point_active = false; + u32 dpi_x = 72; + u32 dpi_y = 72; + u32 font_set_type = 0; + std::filesystem::path system_font_path; + u32 system_font_id = 0; + float system_font_scale_factor = 1.0f; + s32 system_font_shift_value = 0; + struct SystemFallbackFace { + u32 font_id = 0; + float scale_factor = 1.0f; + s32 shift_value = 0; + std::filesystem::path path; + std::shared_ptr> bytes; + FT_Face ft_face = nullptr; + bool ready = false; + }; + std::vector system_fallback_faces; + Libraries::Font::OrbisFontLib library = nullptr; + bool ext_face_ready = false; + std::vector ext_face_data; + FT_Face ext_ft_face = nullptr; + float ext_scale_for_height = 0.0f; + int ext_ascent = 0, ext_descent = 0, ext_lineGap = 0; + int ext_units_per_em = 0; + int ext_typo_ascent = 0, ext_typo_descent = 0, ext_typo_lineGap = 0; + bool ext_use_typo_metrics = false; + std::unordered_map ext_cache; + std::vector scratch; + bool logged_ext_use = false; + float cached_baseline_y = 0.0f; + bool layout_cached = false; + Libraries::Font::OrbisFontRenderer bound_renderer = nullptr; + bool system_requested = false; + std::shared_ptr> fontset_record_storage; + std::shared_ptr fontset_selector; + float effect_weight_x = 1.0f; + float effect_weight_y = 1.0f; + u32 effect_weight_mode = 0; + float effect_slant = 0.0f; + float render_effect_weight_x = 1.0f; + float render_effect_weight_y = 1.0f; + u32 render_effect_weight_mode = 0; + float render_effect_slant = 0.0f; + + ~FontState(); +}; + +using FontAllocFn = void*(PS4_SYSV_ABI*)(void* object, u32 size); +using FontFreeFn = void(PS4_SYSV_ABI*)(void* object, void* p); + +struct FtExternalFaceObj { + u32 refcount; + u32 reserved04; + u32 reserved08; + u32 sub_font_index; + u64 reserved10; + FtExternalFaceObj* next; + u64 reserved20; + u64 reserved28; + FT_Face face; + const u8* font_data; + u32 font_size; + u32 sfnt_base; +}; +static_assert(offsetof(FtExternalFaceObj, sub_font_index) == 0x0C); +static_assert(offsetof(FtExternalFaceObj, next) == 0x18); +static_assert(offsetof(FtExternalFaceObj, face) == 0x30); + +struct LibraryState { + bool support_system = false; + bool support_external = false; + u32 external_formats = 0; + u32 external_fontMax = 0; + const Libraries::Font::OrbisFontMem* backing_memory = nullptr; + Libraries::Font::OrbisFontLibCreateParams create_params = nullptr; + u64 edition = 0; + void* alloc_ctx = nullptr; + FontAllocFn alloc_fn = nullptr; + FontFreeFn free_fn = nullptr; + void* owned_mspace = nullptr; + u32 owned_mspace_size = 0; + void* owned_sysfonts_ctx = nullptr; + u32 owned_sysfonts_ctx_size = 0; + void* owned_external_fonts_ctx = nullptr; + u32 owned_external_fonts_ctx_size = 0; + FontLibOpaque* native = nullptr; + void* owned_device_cache = nullptr; + u32 owned_device_cache_size = 0; +}; + +struct FaceMetrics { + int units_per_em = 0; + int hhea_ascent = 0; + int hhea_descent = 0; + int hhea_lineGap = 0; + int typo_ascent = 0; + int typo_descent = 0; + int typo_lineGap = 0; + bool has_typo = false; + bool use_typo = false; +}; + +struct GeneratedGlyph { + OrbisFontGlyphOpaque glyph{}; + OrbisFontGlyphMetrics metrics{}; + OrbisFontGlyphMetricsHorizontal metrics_horizontal{}; + OrbisFontGlyphMetricsHorizontalX metrics_horizontal_x{}; + OrbisFontGlyphMetricsHorizontalAdvance metrics_horizontal_adv{}; + float origin_x = 0.0f; + float origin_y = 0.0f; + float scale_x_used = 0.0f; + float scale_y_used = 0.0f; + int bbox_x0 = 0; + int bbox_y0 = 0; + int bbox_w = 0; + int bbox_h = 0; + u32 codepoint = 0; + Libraries::Font::OrbisFontHandle owner_handle{}; + OrbisFontGlyphOutline outline{}; + std::vector outline_points; + std::vector outline_tags; + std::vector outline_contours; + bool metrics_initialized = false; + bool outline_initialized = false; +}; + +struct SysDriver { + using PixelResFn = u32(PS4_SYSV_ABI*)(); + using InitFn = s32(PS4_SYSV_ABI*)(const void* memory, void* library); + using TermFn = s32(PS4_SYSV_ABI*)(void* library); + using SupportFn = s32(PS4_SYSV_ABI*)(void* library, u32 formats); + using OpenFn = s32(PS4_SYSV_ABI*)(void* library, u32 mode, const void* fontAddress, + u32 fontSize, u32 subFontIndex, u32 uniqueWord, + void** inoutFontObj); + using CloseFn = s32(PS4_SYSV_ABI*)(void* fontObj, u32 flags); + using ScaleFn = s32(PS4_SYSV_ABI*)(void* fontObj, u16* outUnitsPerEm, float* outScale); + using MetricFn = s32(PS4_SYSV_ABI*)(void* fontObj, u32 metricId, u16* outMetric); + using GlyphsCountFn = s32(PS4_SYSV_ABI*)(void* fontObj, u32* out_count); + using GlyphIndexFn = s32(PS4_SYSV_ABI*)(void* fontObj, u32 codepoint_u16, u32* out_glyph_index); + + using SetCharWithDpiFn = s32(PS4_SYSV_ABI*)(void* fontObj, u32 dpi_x, u32 dpi_y, float scale_x, + float scale_y, float* out_scale_x, + float* out_scale_y); + using SetCharDefaultDpiFn = s32(PS4_SYSV_ABI*)(void* fontObj, float scale_x, float scale_y, + float* out_scale_x, float* out_scale_y); + using ComputeLayoutFn = s32(PS4_SYSV_ABI*)(void* fontObj, const void* style_state_block, + u8 (*out_words)[16]); + using ComputeLayoutAltFn = s32(PS4_SYSV_ABI*)(void* fontObj, const void* style_state_block, + u8 (*out_words)[16]); + + using LoadGlyphCachedFn = s32(PS4_SYSV_ABI*)(void* fontObj, u32 glyphIndex, s32 mode, + std::uint64_t* out_words); + using GetGlyphMetricsFn = s32(PS4_SYSV_ABI*)( + void* fontObj, std::uint32_t* opt_param2, std::uint8_t mode, std::uint8_t* out_params, + Libraries::Font::OrbisFontGlyphMetrics* out_metrics); + using ApplyGlyphAdjustFn = s32(PS4_SYSV_ABI*)(void* fontObj, u32 p2, u32 glyphIndex, s32 p4, + s32 p5, u32* inoutGlyphIndex); + using ConfigureGlyphFn = s32(PS4_SYSV_ABI*)(void* fontObj, std::uint32_t* in_params, s32 mode, + std::uint32_t* inout_state); + + /*0x00*/ u8 reserved_00[0x10]; + /*0x10*/ PixelResFn pixel_resolution; + /*0x18*/ InitFn init; + /*0x20*/ TermFn term; + /*0x28*/ SupportFn support_formats; + /*0x30*/ u8 reserved_30[0x08]; + /*0x38*/ OpenFn open; + /*0x40*/ CloseFn close; + /*0x48*/ u8 reserved_48[0x08]; + /*0x50*/ ScaleFn scale; + /*0x58*/ u8 reserved_58[0x08]; + /*0x60*/ MetricFn metric; + /*0x68*/ GlyphsCountFn glyphs_count; + /*0x70*/ u8 reserved_70[0x08]; + /*0x78*/ GlyphIndexFn glyph_index; + /*0x80*/ SetCharWithDpiFn set_char_with_dpi; + /*0x88*/ SetCharDefaultDpiFn set_char_default_dpi; + /*0x90*/ u8 reserved_90[0x10]; + /*0xA0*/ ComputeLayoutFn compute_layout; + /*0xA8*/ LoadGlyphCachedFn load_glyph_cached; + /*0xB0*/ u8 reserved_B0[0x08]; + /*0xB8*/ GetGlyphMetricsFn get_glyph_metrics; + /*0xC0*/ u8 reserved_C0[0x20]; + /*0xE0*/ ApplyGlyphAdjustFn apply_glyph_adjust; + /*0xE8*/ u8 reserved_E8[0x20]; + /*0x108*/ ConfigureGlyphFn configure_glyph; + /*0x110*/ u8 reserved_0x110_pad[0x08]; + /*0x118*/ ComputeLayoutAltFn compute_layout_alt; +}; +static_assert(sizeof(SysDriver) == 0x120, "SysDriver size"); +static_assert(offsetof(SysDriver, pixel_resolution) == 0x10, "SysDriver pixel_resolution offset"); +static_assert(offsetof(SysDriver, init) == 0x18, "SysDriver init offset"); +static_assert(offsetof(SysDriver, term) == 0x20, "SysDriver term offset"); +static_assert(offsetof(SysDriver, support_formats) == 0x28, "SysDriver support_formats offset"); +static_assert(offsetof(SysDriver, open) == 0x38, "SysDriver open offset"); +static_assert(offsetof(SysDriver, close) == 0x40, "SysDriver close offset"); +static_assert(offsetof(SysDriver, scale) == 0x50, "SysDriver scale offset"); +static_assert(offsetof(SysDriver, metric) == 0x60, "SysDriver metric offset"); +static_assert(offsetof(SysDriver, glyphs_count) == 0x68, "SysDriver glyphs_count offset"); +static_assert(offsetof(SysDriver, glyph_index) == 0x78, "SysDriver glyph_index offset"); +static_assert(offsetof(SysDriver, set_char_with_dpi) == 0x80, "SysDriver set_char_with_dpi offset"); +static_assert(offsetof(SysDriver, set_char_default_dpi) == 0x88, + "SysDriver set_char_default_dpi offset"); +static_assert(offsetof(SysDriver, compute_layout) == 0xA0, "SysDriver compute_layout offset"); +static_assert(offsetof(SysDriver, load_glyph_cached) == 0xA8, "SysDriver load_glyph_cached offset"); +static_assert(offsetof(SysDriver, get_glyph_metrics) == 0xB8, "SysDriver get_glyph_metrics offset"); +static_assert(offsetof(SysDriver, apply_glyph_adjust) == 0xE0, + "SysDriver apply_glyph_adjust offset"); +static_assert(offsetof(SysDriver, configure_glyph) == 0x108, "SysDriver configure_glyph offset"); +static_assert(offsetof(SysDriver, compute_layout_alt) == 0x118, + "SysDriver compute_layout_alt offset"); +struct StyleStateBlock { + /*0x00*/ u32 dpi_x; + /*0x04*/ u32 dpi_y; + /*0x08*/ u32 scale_unit; + /*0x0C*/ u32 reserved_0x0c; + /*0x10*/ float scale_w; + /*0x14*/ float scale_h; + /*0x18*/ float effect_weight_x; + /*0x1C*/ float effect_weight_y; + /*0x20*/ float slant_ratio; + /*0x24*/ float reserved_0x24; +}; +static_assert(sizeof(StyleStateBlock) == 0x28, "StyleStateBlock size"); +static_assert(offsetof(StyleStateBlock, dpi_x) == 0x00, "StyleStateBlock dpi_x offset"); +static_assert(offsetof(StyleStateBlock, dpi_y) == 0x04, "StyleStateBlock dpi_y offset"); +static_assert(offsetof(StyleStateBlock, scale_unit) == 0x08, "StyleStateBlock scale_unit offset"); +static_assert(offsetof(StyleStateBlock, scale_w) == 0x10, "StyleStateBlock scale_w offset"); +static_assert(offsetof(StyleStateBlock, scale_h) == 0x14, "StyleStateBlock scale_h offset"); +static_assert(offsetof(StyleStateBlock, effect_weight_x) == 0x18, + "StyleStateBlock effect_weight_x offset"); +static_assert(offsetof(StyleStateBlock, effect_weight_y) == 0x1C, + "StyleStateBlock effect_weight_y offset"); +static_assert(offsetof(StyleStateBlock, slant_ratio) == 0x20, "StyleStateBlock slant_ratio offset"); + +struct FontSetSelector { + static constexpr u32 kMagic = 0x53464C42u; + + struct Candidate { + u32 font_id = 0xffffffffu; + FT_Face face = nullptr; + }; + + u32 magic = kMagic; + u32 font_set_type = 0; + void* library = nullptr; + u32 mode_low = 0; + u32 sub_font_index = 0; + u32 primary_font_id = 0xffffffffu; + u32 roman_font_id = 0xffffffffu; + u32 arabic_font_id = 0xffffffffu; + std::vector candidates; +}; + +struct FontSetRecordHeader { + /*0x00*/ u32 font_set_type; + /*0x04*/ u8 reserved_0x04[0x18]; + /*0x1C*/ u32 magic; + /*0x20*/ u32 entry_count; +}; +static_assert(sizeof(FontSetRecordHeader) == 0x24, "FontSetRecordHeader size"); +static_assert(offsetof(FontSetRecordHeader, font_set_type) == 0x00, + "FontSetRecordHeader font_set_type offset"); +static_assert(offsetof(FontSetRecordHeader, magic) == 0x1C, "FontSetRecordHeader magic offset"); +static_assert(offsetof(FontSetRecordHeader, entry_count) == 0x20, + "FontSetRecordHeader entry_count offset"); + +struct FontSetRecordView { + const FontSetRecordHeader* header = nullptr; + const u32* entry_indices = nullptr; + const FontSetSelector* selector = nullptr; +}; + +inline std::size_t FontSetRecordSelectorOffset(u32 entry_count) { + const std::size_t indices_bytes = static_cast(entry_count) * sizeof(u32); + const std::size_t ptr_off_unaligned = sizeof(FontSetRecordHeader) + indices_bytes; + const std::size_t ptr_align = alignof(const FontSetSelector*); + return (ptr_off_unaligned + (ptr_align - 1u)) & ~(ptr_align - 1u); +} + +inline FontSetRecordView MakeFontSetRecordView(const FontSetRecordHeader* record) { + if (!record) { + return {}; + } + FontSetRecordView view{}; + view.header = record; + view.entry_indices = reinterpret_cast(static_cast(record + 1)); + const std::size_t ptr_off = FontSetRecordSelectorOffset(record->entry_count); + view.selector = *reinterpret_cast( + static_cast(reinterpret_cast(record) + ptr_off)); + return view; +} + +struct FontCtxHeader { + /*0x00*/ u32 lock_word; + /*0x04*/ u32 max_entries; + /*0x08*/ void* base; + /*0x10*/ u8 reserved_0x10[0x10]; +}; +static_assert(sizeof(FontCtxHeader) == 0x20, "FontCtxHeader size"); +static_assert(offsetof(FontCtxHeader, lock_word) == 0x00, "FontCtxHeader lock_word offset"); +static_assert(offsetof(FontCtxHeader, max_entries) == 0x04, "FontCtxHeader max_entries offset"); +static_assert(offsetof(FontCtxHeader, base) == 0x08, "FontCtxHeader base offset"); + +struct FontCtxEntry { + /*0x00*/ u32 reserved_0x00; + /*0x04*/ u32 active; + /*0x08*/ u64 font_address; + /*0x10*/ u32 unique_id; + /*0x14*/ u32 lock_mode1; + /*0x18*/ u32 lock_mode3; + /*0x1C*/ u32 lock_mode2; + /*0x20*/ void* obj_mode1; + /*0x28*/ void* obj_mode3; + /*0x30*/ void* obj_mode2; + /*0x38*/ u8 reserved_0x38[0x08]; +}; +static_assert(sizeof(FontCtxEntry) == 0x40, "FontCtxEntry size"); +static_assert(offsetof(FontCtxEntry, lock_mode1) == 0x14, "FontCtxEntry lock_mode1 offset"); +static_assert(offsetof(FontCtxEntry, lock_mode3) == 0x18, "FontCtxEntry lock_mode3 offset"); +static_assert(offsetof(FontCtxEntry, lock_mode2) == 0x1C, "FontCtxEntry lock_mode2 offset"); +static_assert(offsetof(FontCtxEntry, obj_mode1) == 0x20, "FontCtxEntry obj_mode1 offset"); +static_assert(offsetof(FontCtxEntry, obj_mode3) == 0x28, "FontCtxEntry obj_mode3 offset"); +static_assert(offsetof(FontCtxEntry, obj_mode2) == 0x30, "FontCtxEntry obj_mode2 offset"); + +struct FontObj { + /*0x00*/ u32 refcount; + /*0x04*/ u32 reserved_0x04; + /*0x08*/ u32 reserved_0x08; + /*0x0C*/ u32 sub_font_index; + /*0x10*/ FontObj* prev; + /*0x18*/ FontObj* next; + /*0x20*/ u8 reserved_0x20[0x08]; + /*0x28*/ void* open_ctx_0x28; + /*0x30*/ void* ft_face; + /*0x38*/ Libraries::Font::OrbisFontHandle font_handle; + /*0x40*/ s32 shift_units_x; + /*0x44*/ s32 shift_units_y; + /*0x48*/ std::uint64_t layout_seed_pair; + /*0x50*/ float scale_x_0x50; + /*0x54*/ float scale_y_0x54; + /*0x58*/ void* ft_ctx_0x58; + /*0x60*/ u8 reserved_0x60[0x04]; + /*0x64*/ s32 cached_glyph_index_0x64; + /*0x68*/ std::uint64_t cached_units_x_0x68; + /*0x70*/ std::uint64_t cached_units_y_0x70; + /*0x78*/ s64 shift_cache_x; + /*0x80*/ s64 shift_cache_y; + /*0x88*/ std::array layout_seed_vec; + /*0x98*/ std::array layout_scale_vec; + /*0xA8*/ u8 reserved_0xA8[0x130 - 0xA8]; + /*0x130*/ u32 glyph_cfg_word_0x130; + /*0x134*/ u8 glyph_cfg_mode_0x134; + /*0x135*/ u8 glyph_cfg_byte_0x135; + /*0x136*/ u8 glyph_cfg_byte_0x136; + /*0x137*/ u8 reserved_0x137[0x200 - 0x137]; +}; +static_assert(offsetof(FontObj, sub_font_index) == 0x0C, "FontObj sub_font_index offset"); +static_assert(offsetof(FontObj, next) == 0x18, "FontObj next offset"); +static_assert(offsetof(FontObj, ft_face) == 0x30, "FontObj ft_face offset"); +static_assert(offsetof(FontObj, font_handle) == 0x38, "FontObj font_handle offset"); +static_assert(offsetof(FontObj, shift_units_x) == 0x40, "FontObj shift_units_x offset"); +static_assert(offsetof(FontObj, shift_units_y) == 0x44, "FontObj shift_units_y offset"); +static_assert(offsetof(FontObj, layout_seed_pair) == 0x48, "FontObj layout_seed_pair offset"); +static_assert(offsetof(FontObj, shift_cache_x) == 0x78, "FontObj shift_cache_x offset"); +static_assert(offsetof(FontObj, shift_cache_y) == 0x80, "FontObj shift_cache_y offset"); +static_assert(offsetof(FontObj, layout_seed_vec) == 0x88, "FontObj layout_seed_vec offset"); +static_assert(offsetof(FontObj, layout_scale_vec) == 0x98, "FontObj layout_scale_vec offset"); +static_assert(offsetof(FontObj, glyph_cfg_word_0x130) == 0x130, "FontObj glyph_cfg_word offset"); +static_assert(sizeof(FontObj) == 0x200, "FontObj size"); + +struct SystemFontDefinition { + u32 font_set_type; + const char* config_key; + const char* default_file; +}; + +struct FontSetCache { + bool loaded = false; + bool available = false; + std::vector bytes; + std::filesystem::path path; +}; + +struct FontLibOpaque { + u16 magic; + u16 reserved0; + u32 lock_word; + u32 flags; + u8 reserved1[0x14]; + void* alloc_ctx; + void** alloc_vtbl; + u8 reserved2[0x50]; + void* sys_driver; + void* fontset_registry; + union { + u8 reserved3[0x10]; + std::array sysfonts_ctx_name_overrides; + }; + void* sysfonts_ctx; + void* external_fonts_ctx; + u32* device_cache_buf; +}; +static_assert(sizeof(FontLibOpaque) == 0xB8, "FontLibOpaque size"); +static_assert(offsetof(FontLibOpaque, alloc_ctx) == 0x20, "FontLibOpaque alloc_ctx offset"); +static_assert(offsetof(FontLibOpaque, alloc_vtbl) == 0x28, "FontLibOpaque alloc_vtbl offset"); +static_assert(offsetof(FontLibOpaque, sys_driver) == 0x80, "FontLibOpaque sys_driver offset"); +static_assert(offsetof(FontLibOpaque, fontset_registry) == 0x88, + "FontLibOpaque fontset_registry offset"); +static_assert(offsetof(FontLibOpaque, sysfonts_ctx) == 0xA0, "FontLibOpaque sysfonts_ctx offset"); +static_assert(offsetof(FontLibOpaque, external_fonts_ctx) == 0xA8, + "FontLibOpaque external_fonts_ctx offset"); +static_assert(offsetof(FontLibOpaque, device_cache_buf) == 0xB0, + "FontLibOpaque device_cache_buf offset"); + +#pragma pack(push, 1) +struct FontLibReserved1Main { + /*0x00*/ u32 reserved_0x00; + /*0x04*/ u32 mem_kind; + /*0x08*/ u32 region_size; + /*0x0C*/ void* region_base; +}; +#pragma pack(pop) +static_assert(sizeof(FontLibReserved1Main) == sizeof(((FontLibOpaque*)nullptr)->reserved1), + "FontLibReserved1Main size"); +static_assert(offsetof(FontLibReserved1Main, mem_kind) == 0x04, + "FontLibReserved1Main mem_kind offset"); +static_assert(offsetof(FontLibReserved1Main, region_size) == 0x08, + "FontLibReserved1Main region_size offset"); +static_assert(offsetof(FontLibReserved1Main, region_base) == 0x0C, + "FontLibReserved1Main region_base offset"); + +struct FontLibReserved2Iface { + /*0x00*/ u8 reserved_00[0x20]; + /*0x20*/ std::uintptr_t alloc_fn; + /*0x28*/ std::uintptr_t dealloc_fn; + /*0x30*/ std::uintptr_t realloc_fn; + /*0x38*/ std::uintptr_t calloc_fn; + /*0x40*/ u8 reserved_40[0x10]; +}; +static_assert(sizeof(FontLibReserved2Iface) == sizeof(((FontLibOpaque*)nullptr)->reserved2), + "FontLibReserved2Iface size"); +static_assert(offsetof(FontLibReserved2Iface, alloc_fn) == 0x20, + "FontLibReserved2Iface alloc_fn offset"); +static_assert(offsetof(FontLibReserved2Iface, dealloc_fn) == 0x28, + "FontLibReserved2Iface dealloc_fn offset"); +static_assert(offsetof(FontLibReserved2Iface, realloc_fn) == 0x30, + "FontLibReserved2Iface realloc_fn offset"); +static_assert(offsetof(FontLibReserved2Iface, calloc_fn) == 0x38, + "FontLibReserved2Iface calloc_fn offset"); + +struct FontLibTail { + /*0x00*/ u8 reserved_00[0x04]; + /*0x04*/ u32 workspace_size; + /*0x08*/ void* workspace; + /*0x10*/ u8 reserved_10[0x10]; + /*0x20*/ void* list_head_ptr; + /*0x28*/ Libraries::Font::OrbisFontHandle list_head; + /*0x30*/ u8 reserved_30[0x18]; +}; +static_assert(offsetof(FontLibTail, workspace_size) == 0x04, "FontLibTail workspace_size offset"); +static_assert(offsetof(FontLibTail, workspace) == 0x08, "FontLibTail workspace offset"); +static_assert(offsetof(FontLibTail, list_head_ptr) == 0x20, "FontLibTail list_head_ptr offset"); +static_assert(offsetof(FontLibTail, list_head) == 0x28, "FontLibTail list_head offset"); +static_assert(sizeof(FontLibTail) == 0x48, "FontLibTail size"); + +#pragma pack(push, 1) +struct FontLibReserved1SysfontTail { + /*0x00*/ u32 reserved_0x00; + /*0x04*/ void* sysfont_desc_ptr; + /*0x0C*/ u64 sysfont_flags; +}; +#pragma pack(pop) +static_assert(sizeof(FontLibReserved1SysfontTail) == sizeof(((FontLibOpaque*)nullptr)->reserved1), + "FontLibReserved1SysfontTail size"); +static_assert(offsetof(FontLibReserved1SysfontTail, sysfont_desc_ptr) == 0x04, + "FontLibReserved1SysfontTail sysfont_desc_ptr offset"); +static_assert(offsetof(FontLibReserved1SysfontTail, sysfont_flags) == 0x0C, + "FontLibReserved1SysfontTail sysfont_flags offset"); + +struct RendererOpaque { + /*0x00*/ u16 magic; + /*0x02*/ u16 reserved02; + /*0x04*/ u8 reserved_04[0x08]; + /*0x0C*/ u8 feature_byte_0x0c; + /*0x0D*/ u8 reserved_0d[0x03]; + /*0x10*/ u32 mem_kind; + /*0x14*/ u32 region_size; + /*0x18*/ void* region_base; + /*0x20*/ void* alloc_ctx; + /*0x28*/ const OrbisFontMemInterface* mem_iface; + /*0x30*/ u8 reserved_30[0x20]; + /*0x50*/ std::uintptr_t alloc_fn; + /*0x58*/ std::uintptr_t free_fn; + /*0x60*/ std::uintptr_t realloc_fn; + /*0x68*/ std::uintptr_t calloc_fn; + /*0x70*/ u8 reserved_70[0x10]; + /*0x80*/ void* selection; + /*0x88*/ u8 reserved_88[0x08]; + /*0x90*/ u64 outline_magic_0x90; + /*0x98*/ void* workspace; + /*0xA0*/ u64 workspace_size; + /*0xA8*/ u32 reserved_a8; + /*0xAC*/ u32 outline_policy_flag; +}; +static_assert(sizeof(RendererOpaque) == 0xB0, "RendererOpaque size"); +static_assert(offsetof(RendererOpaque, magic) == 0x00, "RendererOpaque magic offset"); +static_assert(offsetof(RendererOpaque, feature_byte_0x0c) == 0x0C, + "RendererOpaque feature byte offset"); +static_assert(offsetof(RendererOpaque, mem_kind) == 0x10, "RendererOpaque mem_kind offset"); +static_assert(offsetof(RendererOpaque, region_size) == 0x14, "RendererOpaque region_size offset"); +static_assert(offsetof(RendererOpaque, alloc_ctx) == 0x20, "RendererOpaque alloc_ctx offset"); +static_assert(offsetof(RendererOpaque, mem_iface) == 0x28, "RendererOpaque mem_iface offset"); +static_assert(offsetof(RendererOpaque, alloc_fn) == 0x50, "RendererOpaque alloc_fn offset"); +static_assert(offsetof(RendererOpaque, free_fn) == 0x58, "RendererOpaque free_fn offset"); +static_assert(offsetof(RendererOpaque, selection) == 0x80, "RendererOpaque selection offset"); +static_assert(offsetof(RendererOpaque, workspace) == 0x98, "RendererOpaque workspace offset"); +static_assert(offsetof(RendererOpaque, reserved_a8) == 0xA8, "RendererOpaque reserved_a8 offset"); +static_assert(offsetof(RendererOpaque, outline_policy_flag) == 0xAC, + "RendererOpaque outline_policy_flag offset"); + +struct RendererFtBackend { + /*0x00*/ void* renderer_header_0x10; + /*0x08*/ std::uintptr_t unknown_0x08; + /*0x10*/ std::uintptr_t unknown_0x10; + /*0x18*/ std::uintptr_t unknown_0x18; + /*0x20*/ void* initialized_marker; + /*0x28*/ u8 reserved_0x28[0x20]; +}; +static_assert(sizeof(RendererFtBackend) == 0x48, "RendererFtBackend size"); +static_assert(offsetof(RendererFtBackend, renderer_header_0x10) == 0x00, + "RendererFtBackend renderer_header_0x10 offset"); +static_assert(offsetof(RendererFtBackend, initialized_marker) == 0x20, + "RendererFtBackend initialized_marker offset"); + +struct RendererFtOpaque { + /*0x000*/ RendererOpaque base; + /*0x0B0*/ u8 reserved_0x0B0[0x70]; + /*0x120*/ RendererFtBackend ft_backend; +}; +static_assert(sizeof(RendererFtOpaque) == 0x168, "RendererFtOpaque size"); +static_assert(offsetof(RendererFtOpaque, ft_backend) == 0x120, + "RendererFtOpaque ft_backend offset"); + +struct OrbisFontRenderer_ {}; + +struct StyleFrameScaleState { + float scale_w; + float scale_h; + u32 dpi_x; + u32 dpi_y; + bool scale_overridden; + bool dpi_overridden; +}; + +extern const float kPointsPerInch; +extern const float kScaleEpsilon; + +extern const u16 kStyleFrameMagic; +extern const u8 kStyleFrameFlagScale; +extern const u8 kStyleFrameFlagWeight; +extern const u8 kStyleFrameFlagSlant; + +extern Core::FileSys::MntPoints* g_mnt; +extern std::unordered_map g_font_state; +extern std::unordered_map g_library_state; +extern std::unordered_map + g_style_for_surface; +extern std::mutex g_generated_glyph_mutex; +extern std::unordered_set g_generated_glyphs; + +extern void* g_allocator_vtbl_stub[4]; +extern std::uint8_t g_sys_driver_stub; +extern std::uint8_t g_fontset_registry_stub; +extern std::uint8_t g_sysfonts_ctx_stub; +extern std::uint8_t g_external_fonts_ctx_stub; +extern u32 g_device_cache_stub; + +FontState& GetState(Libraries::Font::OrbisFontHandle h); +FontState* TryGetState(Libraries::Font::OrbisFontHandle h); +void RemoveState(Libraries::Font::OrbisFontHandle h); +LibraryState& GetLibState(Libraries::Font::OrbisFontLib lib); +void RemoveLibState(Libraries::Font::OrbisFontLib lib); +bool AcquireLibraryLock(FontLibOpaque* lib, u32& out_prev_lock_word); +void ReleaseLibraryLock(FontLibOpaque* lib, u32 prev_lock_word); +FT_Face CreateFreeTypeFaceFromBytes(const unsigned char* data, std::size_t size, u32 subfont_index); +void DestroyFreeTypeFace(FT_Face& face); +void LogFontOpenError(s32 rc); +void LogExternalFormatSupport(u32 formats_mask); +std::optional ResolveKnownSysFontAlias( + const std::filesystem::path& sysfonts_dir, std::string_view ps4_filename); +void LogFontOpenParams(const Libraries::Font::OrbisFontOpenParams* params); +std::filesystem::path ResolveGuestPath(const char* guest_path); +bool LoadGuestFileBytes(const std::filesystem::path& host_path, + std::vector& out_bytes); +FaceMetrics LoadFaceMetrics(FT_Face face); +void PopulateStateMetrics(FontState& st, const FaceMetrics& m); +float ComputeScaleExtForState(const FontState& st, FT_Face face, float pixel_h); +float ComputeScaleForPixelHeight(const FontState& st, float pixel_h); +float SafeDpiToFloat(u32 dpi); +float PointsToPixels(float pt, u32 dpi); +float PixelsToPoints(float px, u32 dpi); +u16 ClampToU16(float value); +StyleFrameScaleState ResolveStyleFrameScale(const Libraries::Font::OrbisFontStyleFrame* style, + const FontState& st); +void InitializeStyleFrame(Libraries::Font::OrbisFontStyleFrame& frame); +bool EnsureStyleFrameInitialized(Libraries::Font::OrbisFontStyleFrame* frame); +bool ValidateStyleFramePtr(const Libraries::Font::OrbisFontStyleFrame* frame); +void UpdateCachedLayout(FontState& st); +std::uint64_t MakeGlyphKey(u32 code, int pixel_h); +void LogStrideOnce(const Libraries::Font::OrbisFontRenderSurface* surf); +void ClearRenderOutputs(Libraries::Font::OrbisFontGlyphMetrics* metrics, + Libraries::Font::OrbisFontRenderOutput* result); +bool ResolveFaceAndScale(FontState& st, Libraries::Font::OrbisFontHandle handle, float pixel_h, + FT_Face& face_out, float& scale_y_out); +bool ResolveFace(FontState& st, Libraries::Font::OrbisFontHandle handle, FT_Face& face_out); +s32 RenderCodepointToSurface(FontState& st, Libraries::Font::OrbisFontHandle handle, FT_Face face, + float pixel_w, float pixel_h, + Libraries::Font::OrbisFontRenderSurface* surf, u32 code, float x, + float y, Libraries::Font::OrbisFontGlyphMetrics* metrics, + Libraries::Font::OrbisFontRenderOutput* result, s32 shift_x_units, + s32 shift_y_units); +s32 RenderCodepointToSurfaceWithScale(FontState& st, Libraries::Font::OrbisFontHandle handle, + FT_Face face, float scale_x, float scale_y, + Libraries::Font::OrbisFontRenderSurface* surf, u32 code, + float x, float y, Libraries::Font::OrbisFontGlyphMetrics* m, + Libraries::Font::OrbisFontRenderOutput* result); +const GlyphEntry* GetGlyphEntry(FontState& st, Libraries::Font::OrbisFontHandle handle, u32 code, + FT_Face& face_out, float& scale_out); +const SystemFontDefinition* FindSystemFontDefinition(u32 font_set_type); +std::filesystem::path GetSysFontBaseDir(); +std::filesystem::path ResolveSystemFontPath(u32 font_set_type); +const struct FontSetCache* EnsureFontSetCache(u32 font_set_type); +bool HasSfntTables(const std::vector& bytes); +bool LoadFontFile(const std::filesystem::path& path, std::vector& out_bytes); +bool AttachSystemFont(FontState& st, Libraries::Font::OrbisFontHandle handle); +std::string ReportSystemFaceRequest(FontState& st, Libraries::Font::OrbisFontHandle handle, + bool& attached_out); +void TrackGeneratedGlyph(Libraries::Font::OrbisFontGlyph glyph); +bool ForgetGeneratedGlyph(Libraries::Font::OrbisFontGlyph glyph); +GeneratedGlyph* TryGetGeneratedGlyph(Libraries::Font::OrbisFontGlyph glyph); +void PopulateGlyphMetricVariants(GeneratedGlyph& gg); +void BuildBoundingOutline(GeneratedGlyph& gg); +bool BuildTrueOutline(GeneratedGlyph& gg); +void LogCachedStyleOnce(Libraries::Font::OrbisFontHandle handle, + const Libraries::Font::FontHandleOpaqueNative& font); +void LogRenderResultSample(Libraries::Font::OrbisFontHandle handle, u32 code, + const Libraries::Font::OrbisFontGlyphMetrics& metrics, + const Libraries::Font::OrbisFontRenderOutput& result); +u8 CachedStyleCacheFlags(const Libraries::Font::OrbisFontStyleFrame& cached_style); +void CachedStyleSetCacheFlags(Libraries::Font::OrbisFontStyleFrame& cached_style, u8 flags); +void CachedStyleSetDirectionWord(Libraries::Font::OrbisFontStyleFrame& cached_style, u16 word); +float CachedStyleGetScalar(const Libraries::Font::OrbisFontStyleFrame& cached_style); +void CachedStyleSetScalar(Libraries::Font::OrbisFontStyleFrame& cached_style, float value); + +} // namespace Libraries::Font::Internal diff --git a/src/core/libraries/font/fontft.cpp b/src/core/libraries/font/fontft.cpp index 4a1dbb989..4df91b0f3 100644 --- a/src/core/libraries/font/fontft.cpp +++ b/src/core/libraries/font/fontft.cpp @@ -3,7 +3,10 @@ #include "common/logging/log.h" #include "core/libraries/error_codes.h" +#include "core/libraries/font/font.h" +#include "core/libraries/font/font_internal.h" #include "core/libraries/font/fontft.h" +#include "core/libraries/font/fontft_internal.h" #include "core/libraries/libs.h" namespace Libraries::FontFt { @@ -103,14 +106,22 @@ s32 PS4_SYSV_ABI sceFontSelectGlyphsFt() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontSelectLibraryFt() { - LOG_ERROR(Lib_FontFt, "(STUBBED) called"); - return ORBIS_OK; +const OrbisFontLibrarySelection* PS4_SYSV_ABI sceFontSelectLibraryFt(int value) { + LOG_INFO(Lib_FontFt, "called"); + LOG_DEBUG(Lib_FontFt, "params:\nvalue: {}\n", value); + if (value == 0) { + return Libraries::FontFt::Internal::GetDriverTable(); + } + return nullptr; } -s32 PS4_SYSV_ABI sceFontSelectRendererFt() { - LOG_ERROR(Lib_FontFt, "(STUBBED) called"); - return ORBIS_OK; +const OrbisFontRendererSelection* PS4_SYSV_ABI sceFontSelectRendererFt(int value) { + LOG_INFO(Lib_FontFt, "called"); + LOG_DEBUG(Lib_FontFt, "params:\nvalue: {}\n", value); + if (value == 0) { + return Libraries::FontFt::Internal::GetRendererSelectionTable(); + } + return nullptr; } void RegisterlibSceFontFt(Core::Loader::SymbolsResolver* sym) { @@ -137,4 +148,4 @@ void RegisterlibSceFontFt(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("Xx974EW-QFY", "libSceFontFt", 1, "libSceFontFt", sceFontSelectRendererFt); }; -} // namespace Libraries::FontFt \ No newline at end of file +} // namespace Libraries::FontFt diff --git a/src/core/libraries/font/fontft.h b/src/core/libraries/font/fontft.h index cec6d7872..f749e7724 100644 --- a/src/core/libraries/font/fontft.h +++ b/src/core/libraries/font/fontft.h @@ -11,6 +11,24 @@ class SymbolsResolver; namespace Libraries::FontFt { +struct OrbisFontLibrarySelection { + u32 magic; + u32 reserved; + void* reserved_ptr1; + uintptr_t get_pixel_resolution_fn; + uintptr_t init_fn; + uintptr_t term_fn; + uintptr_t support_fn; +}; + +struct OrbisFontRendererSelection { + u32 magic; + u32 size; + uintptr_t create_fn; + uintptr_t destroy_fn; + uintptr_t query_fn; +}; + s32 PS4_SYSV_ABI sceFontFtInitAliases(); s32 PS4_SYSV_ABI sceFontFtSetAliasFont(); s32 PS4_SYSV_ABI sceFontFtSetAliasPath(); @@ -30,8 +48,8 @@ s32 PS4_SYSV_ABI sceFontFtSupportType42(); s32 PS4_SYSV_ABI sceFontFtSupportWinFonts(); s32 PS4_SYSV_ABI sceFontFtTermAliases(); s32 PS4_SYSV_ABI sceFontSelectGlyphsFt(); -s32 PS4_SYSV_ABI sceFontSelectLibraryFt(); -s32 PS4_SYSV_ABI sceFontSelectRendererFt(); +const OrbisFontLibrarySelection* PS4_SYSV_ABI sceFontSelectLibraryFt(int value); +const OrbisFontRendererSelection* PS4_SYSV_ABI sceFontSelectRendererFt(int value); void RegisterlibSceFontFt(Core::Loader::SymbolsResolver* sym); -} // namespace Libraries::FontFt \ No newline at end of file +} // namespace Libraries::FontFt diff --git a/src/core/libraries/font/fontft_internal.cpp b/src/core/libraries/font/fontft_internal.cpp new file mode 100644 index 000000000..b7d5bacdd --- /dev/null +++ b/src/core/libraries/font/fontft_internal.cpp @@ -0,0 +1,3603 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/libraries/font/fontft_internal.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include FT_FREETYPE_H +#include FT_MODULE_H +#include FT_SYSTEM_H +#include FT_TRUETYPE_TABLES_H + +#include "common/logging/log.h" +#include "common/singleton.h" +#include "core/file_sys/fs.h" +#include "core/libraries/font/font_internal.h" +#include "core/libraries/kernel/kernel.h" +#include "core/tls.h" +#include "font_error.h" + +namespace Libraries::Font::Internal { + +using Libraries::Font::OrbisFontGlyph; +using Libraries::Font::OrbisFontGlyphMetrics; +using Libraries::Font::OrbisFontHandle; +using Libraries::Font::OrbisFontRenderer; +using Libraries::Font::OrbisFontRenderOutput; +using Libraries::Font::OrbisFontRenderSurface; +using Libraries::Font::OrbisFontStyleFrame; + +namespace { +static thread_local GetCharGlyphMetricsFailLogState g_get_char_metrics_fail; + +static void LogGetCharGlyphMetricsFailOnce(std::string_view stage, s32 rc, FT_Error ft_err, + bool is_system, u32 code, FT_UInt glyph_index, + FT_Face face, float scale_w, float scale_h) { + constexpr u32 kMaxDetailedLogs = 16; + if (g_get_char_metrics_fail.count < kMaxDetailedLogs) { + u32 enc = 0; + u16 platform_id = 0; + u16 encoding_id = 0; + u32 num_glyphs = 0; + u32 num_charmaps = 0; + if (face) { + num_glyphs = static_cast(face->num_glyphs); + num_charmaps = static_cast(face->num_charmaps); + if (face->charmap) { + enc = static_cast(face->charmap->encoding); + platform_id = face->charmap->platform_id; + encoding_id = face->charmap->encoding_id; + } + } + + LOG_WARNING( + Lib_Font, + "GetCharGlyphMetricsFail: rc={} stage={} ft_err={} is_system={} code={} glyph_index={} " + "num_glyphs={} cmap(enc={},pid={},eid={}) num_charmaps={} scale_w={} scale_h={}", + rc, stage, static_cast(ft_err), is_system, code, glyph_index, num_glyphs, enc, + platform_id, encoding_id, num_charmaps, scale_w, scale_h); + + ++g_get_char_metrics_fail.count; + return; + } + + if (!g_get_char_metrics_fail.suppression_logged) { + LOG_WARNING(Lib_Font, "GetCharGlyphMetricsFail: further failures suppressed"); + g_get_char_metrics_fail.suppression_logged = true; + } +} +} // namespace + +static void UpdateFtFontObjShiftCache(u8* font_obj) { + if (!font_obj) { + return; + } + + auto* obj = reinterpret_cast(font_obj); + obj->shift_cache_x = static_cast(obj->shift_units_x); + obj->shift_cache_y = static_cast(obj->shift_units_y); + + const s32 seed_low = static_cast(static_cast(obj->layout_seed_pair & 0xFFFFFFFFu)); + const s32 seed_high = + static_cast(static_cast((obj->layout_seed_pair >> 32) & 0xFFFFFFFFu)); + obj->layout_seed_vec[0] = static_cast(static_cast(seed_low)); + obj->layout_seed_vec[1] = static_cast(static_cast(seed_high)); + obj->layout_scale_vec[0] = 0x0000000000010000ull; + obj->layout_scale_vec[1] = 0x0000000000010000ull; +} + +SysFontDesc GetSysFontDesc(u32 font_id) { + (void)font_id; + return SysFontDesc{ + .scale_factor = 1.0f, + .shift_value = 0, + }; +} + +Libraries::Font::FontHandleOpaqueNative* GetNativeFont(Libraries::Font::OrbisFontHandle h) { + return h ? reinterpret_cast(h) : nullptr; +} + +bool AcquireFontLock(Libraries::Font::FontHandleOpaqueNative* font, u32& out_prev_lock_word) { + if (!font) { + return false; + } + for (;;) { + const u32 lock_word = font->lock_word; + if (font->magic != 0x0F02) { + return false; + } + if (static_cast(lock_word) < 0) { + Libraries::Kernel::sceKernelUsleep(0x1e); + continue; + } + std::atomic_ref ref(font->lock_word); + u32 expected = lock_word; + if (ref.compare_exchange_weak(expected, lock_word | 0x80000000u, + std::memory_order_acq_rel)) { + out_prev_lock_word = lock_word; + return true; + } + Libraries::Kernel::sceKernelUsleep(0x1e); + } +} + +void ReleaseFontLock(Libraries::Font::FontHandleOpaqueNative* font, u32 prev_lock_word) { + if (!font) { + return; + } + font->lock_word = prev_lock_word & 0x7fffffff; +} + +bool AcquireCachedStyleLock(Libraries::Font::FontHandleOpaqueNative* font, + u32& out_prev_lock_word) { + if (!font) { + return false; + } + for (;;) { + const u32 lock_word = font->cached_style.cache_lock_word; + if (font->magic != 0x0F02) { + return false; + } + if (static_cast(lock_word) < 0) { + Libraries::Kernel::sceKernelUsleep(0x1e); + continue; + } + std::atomic_ref ref(font->cached_style.cache_lock_word); + u32 expected = lock_word; + if (ref.compare_exchange_weak(expected, lock_word | 0x80000000u, + std::memory_order_acq_rel)) { + out_prev_lock_word = lock_word; + return true; + } + Libraries::Kernel::sceKernelUsleep(0x1e); + } +} + +void ReleaseCachedStyleLock(Libraries::Font::FontHandleOpaqueNative* font, u32 prev_lock_word) { + if (!font) { + return; + } + font->cached_style.cache_lock_word = prev_lock_word & 0x7fffffff; +} + +std::uint8_t* AcquireFontCtxEntry(std::uint8_t* ctx, u32 idx, u32 mode_low, void** out_obj, + u32* out_lock_word) { + if (!ctx || !out_obj || !out_lock_word) { + return nullptr; + } + + auto* header = reinterpret_cast(ctx); + const u32 max = header->max_entries; + if (max < idx || max == idx) { + *out_lock_word = 0; + *out_obj = nullptr; + return nullptr; + } + + auto* base = static_cast(header->base); + const std::size_t entry_off = static_cast(idx) * sizeof(FontCtxEntry); + auto* entry_u8 = base ? (base + entry_off) : nullptr; + auto* entry = entry_u8 ? reinterpret_cast(entry_u8) : nullptr; + if (!entry) { + *out_lock_word = 0; + *out_obj = nullptr; + return nullptr; + } + + u32* lock_word = nullptr; + void** obj_slot = nullptr; + if (mode_low == 3) { + lock_word = &entry->lock_mode3; + obj_slot = &entry->obj_mode3; + } else if (mode_low == 2) { + lock_word = &entry->lock_mode2; + obj_slot = &entry->obj_mode2; + } else if (mode_low == 1) { + lock_word = &entry->lock_mode1; + obj_slot = &entry->obj_mode1; + } else { + *out_lock_word = 0; + *out_obj = nullptr; + return entry_u8; + } + + for (;;) { + const u32 cur = *lock_word; + if (static_cast(cur) >= 0) { + std::atomic_ref ref(*lock_word); + u32 expected = cur; + if (ref.compare_exchange_weak(expected, cur | 0x80000000u, std::memory_order_acq_rel)) { + *out_lock_word = cur | 0x80000000u; + *out_obj = obj_slot ? *obj_slot : nullptr; + return entry_u8; + } + } + Libraries::Kernel::sceKernelUsleep(0x1e); + } +} + +static std::uint8_t* AcquireFontCtxEntryAndSelectNode(std::uint8_t* ctx, u32 idx, u32 mode_low, + u32 sub_font_index, void** out_obj, + u32* out_lock_word) { + if (!out_obj) { + return nullptr; + } + std::uint8_t* entry_u8 = AcquireFontCtxEntry(ctx, idx, mode_low, out_obj, out_lock_word); + auto* node = static_cast(*out_obj); + while (node) { + if (node->sub_font_index == sub_font_index) { + break; + } + node = node->next; + } + *out_obj = node; + return entry_u8; +} + +static void ReleaseFontCtxEntryLock(std::uint8_t* entry_u8, u32 mode_low, u32 lock_word) { + if (!entry_u8) { + return; + } + auto* entry = reinterpret_cast(entry_u8); + u32* lock_word_ptr = nullptr; + if (mode_low == 3) { + if ((lock_word & 0x0FFFFFFFu) == 0) { + entry->obj_mode3 = nullptr; + } + lock_word_ptr = &entry->lock_mode3; + } else if (mode_low == 1) { + if ((lock_word & 0x0FFFFFFFu) == 0) { + entry->obj_mode1 = nullptr; + } + lock_word_ptr = &entry->lock_mode1; + } else { + if ((lock_word & 0x0FFFFFFFu) == 0) { + entry->obj_mode2 = nullptr; + } + lock_word_ptr = &entry->lock_mode2; + } + + if (((lock_word & 0x0FFFFFFFu) == 0) && entry->obj_mode1 == nullptr && + entry->obj_mode3 == nullptr && entry->obj_mode2 == nullptr) { + entry->unique_id = 0; + entry->active = 0; + } + *lock_word_ptr = lock_word & 0x7FFFFFFFu; +} + +static void ApplyGlyphSpecialCaseAdjust(int font_id, int glyph_index, int* inout_seed_words) { + if (!inout_seed_words) { + return; + } + if (((font_id == 10) && (static_cast(glyph_index - 0x23d1U) < 0x5e)) && + ((0x5c0000000017ULL >> + (static_cast(static_cast((static_cast(glyph_index - 0x23d1U) >> 1) & 0x3f)) & + 0x3fULL)) & + 1ULL) != 0) { + inout_seed_words[0] = inout_seed_words[0] + 500; + } +} + +static std::uintptr_t AcquireRendererSelectionLock(RendererOpaque* renderer) { + if (!renderer) { + return 0; + } + + constexpr std::uintptr_t kLocked = std::numeric_limits::max(); + for (;;) { + const std::uintptr_t cur = reinterpret_cast(renderer->selection); + if (cur != kLocked) { + std::atomic_ref ref(renderer->selection); + void* expected = reinterpret_cast(cur); + if (ref.compare_exchange_weak(expected, reinterpret_cast(kLocked), + std::memory_order_acq_rel)) { + return cur; + } + } + Libraries::Kernel::sceKernelUsleep(0x1e); + } +} + +static std::uint64_t ResolveGposTagForCode(u32 codepoint) { + (void)codepoint; + return 0; +} + +static float SysFontScaleFactor(u32 font_id) { + (void)font_id; + return 1.0f; +} + +static float SysFontShiftValueF32(u32 font_id) { + (void)font_id; + return 0.0f; +} + +static u8 SysFontHasAltFlag(u32 font_id) { + (void)font_id; + return 0; +} + +const std::uint8_t* FindSysFontRangeRecord(u32 font_id, u32 codepoint) { + (void)font_id; + (void)codepoint; + return nullptr; +} + +static u32 ResolveSysFontMapping(const void* record, int table_id, u32 codepoint, + u32* out_codepoint) { + (void)record; + (void)table_id; + if (out_codepoint) { + *out_codepoint = codepoint; + } + return 0; +} + +u32 ResolveSysFontCodepoint(const void* record, u64 code, u32 flags, u32* out_font_id) { + if (!record) { + if (out_font_id) { + *out_font_id = 0; + } + return static_cast(code); + } + + const u32 code_u32 = static_cast(code); + + const auto* rec = static_cast(record); + if (rec->magic == Libraries::Font::Internal::FontSetSelector::kMagic) { + const auto view = MakeFontSetRecordView(rec); + const u32 entry_count = view.header ? view.header->entry_count : 0u; + const u32 record_primary_id = + (entry_count && view.entry_indices) ? view.entry_indices[0] : 0u; + const auto* selector = view.selector; + if (!selector || selector->magic != Libraries::Font::Internal::FontSetSelector::kMagic) { + if (out_font_id) { + *out_font_id = record_primary_id; + } + return code_u32; + } + + const u32 primary_id = selector->primary_font_id != 0xffffffffu ? selector->primary_font_id + : record_primary_id; + const u32 roman_id = + selector->roman_font_id != 0xffffffffu ? selector->roman_font_id : record_primary_id; + const u32 arabic_id = + selector->arabic_font_id != 0xffffffffu ? selector->arabic_font_id : roman_id; + + auto is_cjk_like = [](u32 cp) -> bool { + if ((cp >= 0x3040u && cp <= 0x30FFu) || (cp >= 0x31F0u && cp <= 0x31FFu) || + (cp >= 0x3400u && cp <= 0x4DBFu) || (cp >= 0x4E00u && cp <= 0x9FFFu) || + (cp >= 0xAC00u && cp <= 0xD7AFu) || (cp >= 0xF900u && cp <= 0xFAFFu) || + (cp >= 0x20000u && cp <= 0x2FA1Fu)) { + return true; + } + return false; + }; + auto is_arabic_like = [](u32 cp) -> bool { + return (cp >= 0x0600u && cp <= 0x06FFu) || (cp >= 0x0750u && cp <= 0x077Fu) || + (cp >= 0x08A0u && cp <= 0x08FFu) || (cp >= 0xFB50u && cp <= 0xFDFFu) || + (cp >= 0xFE70u && cp <= 0xFEFFu); + }; + auto is_symbol_like = [](u32 cp) -> bool { + return (cp >= 0x25A0u && cp <= 0x25FFu) || (cp >= 0x2600u && cp <= 0x26FFu) || + (cp >= 0x2700u && cp <= 0x27BFu); + }; + + u32 selected_font_id = roman_id; + if (is_arabic_like(code_u32)) { + selected_font_id = arabic_id; + } else if (is_cjk_like(code_u32) || is_symbol_like(code_u32)) { + selected_font_id = primary_id; + } + + if (view.entry_indices && entry_count) { + bool found = false; + for (u32 i = 0; i < entry_count; ++i) { + if (view.entry_indices[i] == selected_font_id) { + found = true; + break; + } + } + if (!found) { + selected_font_id = record_primary_id; + } + } + + if (out_font_id) { + *out_font_id = selected_font_id; + } + (void)flags; + return code_u32; + } + + (void)flags; + if (out_font_id) { + *out_font_id = 0; + } + return code_u32; +} + +void ReleaseFontObjectsForHandle(Libraries::Font::OrbisFontHandle fontHandle, int entryCount) { + if (!fontHandle) { + return; + } + auto* font = GetNativeFont(fontHandle); + if (!font) { + return; + } + auto* lib = static_cast(font->library); + if (!lib || lib->magic != 0x0F01 || !lib->sys_driver) { + return; + } + + const auto* fontset_record = + static_cast(font->open_info.fontset_record); + + const u32* entry_indices = nullptr; + u32 count = 0; + u8* ctx = nullptr; + if (!fontset_record) { + entry_indices = &font->open_info.ctx_entry_index; + count = 1; + ctx = static_cast(lib->external_fonts_ctx); + } else { + if (entryCount < 0) { + entryCount = static_cast(fontset_record->entry_count); + } + entry_indices = reinterpret_cast(fontset_record + 1); + count = static_cast(entryCount); + ctx = static_cast(lib->sysfonts_ctx); + } + + if (!ctx || !entry_indices || count == 0) { + return; + } + + const auto* driver = reinterpret_cast(lib->sys_driver); + const auto close_fn = driver ? driver->close : nullptr; + if (!close_fn) { + return; + } + + const u32 mode_low = static_cast(font->flags) & 0x0Fu; + const u32 sub_font_index = font->open_info.sub_font_index; + + for (u32 n = count; n != 0; --n) { + const u32 font_id = entry_indices[n - 1]; + if (static_cast(font_id) < 0) { + continue; + } + + void* head = nullptr; + u32 lock_word = 0; + u8* entry_u8 = AcquireFontCtxEntry(ctx, font_id, mode_low, &head, &lock_word); + auto* entry = entry_u8 ? reinterpret_cast(entry_u8) : nullptr; + if (!entry) { + continue; + } + + FontObj* match = nullptr; + for (auto* node = static_cast(head); node != nullptr; node = node->next) { + if (node->sub_font_index == sub_font_index) { + match = node; + break; + } + } + + const u32 open_flag = lock_word & 0x40000000u; + const u32 count_bits = lock_word & 0x0FFFFFFFu; + if (open_flag != 0 && count_bits != 0 && match) { + const u32 refcount = match->refcount; + const u32 dec_lock = lock_word - 1; + lock_word = dec_lock; + if (refcount != 0) { + if (refcount == 1) { + const u64 next = reinterpret_cast(match->next); + const u64 prev = reinterpret_cast(match->prev); + (void)close_fn(match, 0); + if (prev == 0) { + if (mode_low == 1) { + entry->obj_mode1 = reinterpret_cast(next); + } else if (mode_low == 2) { + entry->obj_mode2 = reinterpret_cast(next); + } else if (mode_low == 3) { + entry->obj_mode3 = reinterpret_cast(next); + } + } + } else { + match->refcount = refcount - 1; + } + } + if (count_bits == 1) { + lock_word = (~open_flag) & dec_lock; + } + } + + const u32 remaining = lock_word & 0x0FFFFFFFu; + if (mode_low == 3) { + if (remaining == 0) { + entry->obj_mode3 = nullptr; + } + entry->lock_mode3 = lock_word & 0x7fffffffu; + } else if (mode_low == 1) { + if (remaining == 0) { + entry->obj_mode1 = nullptr; + } + entry->lock_mode1 = lock_word & 0x7fffffffu; + } else { + if (remaining == 0) { + entry->obj_mode2 = nullptr; + } + entry->lock_mode2 = lock_word & 0x7fffffffu; + } + + if (remaining == 0 && entry->obj_mode1 == nullptr && entry->obj_mode3 == nullptr && + entry->obj_mode2 == nullptr) { + entry->unique_id = 0; + entry->active = 0; + } + } +} + +s32 ComputeHorizontalLayoutBlocks(OrbisFontHandle fontHandle, const void* style_state_block, + u8 (*out_words)[16]) { + if (out_words) { + std::memset(out_words[0], 0, 16); + std::memset(out_words[1] + 8, 0, 8); + std::memset(out_words[2], 0, 4); + } + + const auto fail_return = [&]() -> s32 { + if (out_words) { + std::memset(out_words[1] + 8, 0, 8); + std::memset(out_words[2], 0, 4); + std::memset(out_words[0], 0, 16); + } + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + }; + + auto* font = GetNativeFont(fontHandle); + if (!font) { + return fail_return(); + } + + auto* lib = static_cast(font->library); + if (!lib) { + return fail_return(); + } + + const auto* fontset_record = + static_cast(font->open_info.fontset_record); + + void* ctx_void = nullptr; + const u32* entry_indices = nullptr; + int entry_count = 0; + if (!fontset_record) { + ctx_void = lib->external_fonts_ctx; + entry_indices = &font->open_info.ctx_entry_index; + entry_count = 1; + } else { + ctx_void = lib->sysfonts_ctx; + entry_count = static_cast(fontset_record->entry_count); + entry_indices = reinterpret_cast(fontset_record + 1); + if (entry_count == 0) { + return fail_return(); + } + } + + auto* ctx = static_cast(ctx_void); + if (!ctx || !entry_indices) { + return fail_return(); + } + + const u32 mode_low = static_cast(font->flags) & 0x0Fu; + const u32 sub_font_index = font->open_info.sub_font_index; + const auto* driver = reinterpret_cast(lib->sys_driver); + if (!driver) { + return fail_return(); + } + const auto set_char_with_dpi = driver->set_char_with_dpi; + const auto set_char_default_dpi = driver->set_char_default_dpi; + const auto compute_layout = driver->compute_layout; + if (!set_char_with_dpi || !set_char_default_dpi || !compute_layout) { + return fail_return(); + } + + const auto* style = static_cast(style_state_block); + if (!style) { + return fail_return(); + } + + float baseline_max = 0.0f; + float delta_max = 0.0f; + float effect_for_baseline = 0.0f; + float effect_for_delta = 0.0f; + + F32x2 acc_u13_i8{}; + F32x2 acc_x_bounds{}; + + const auto load_f32x2_from_u64 = [](u64 bits) -> F32x2 { + const u32 lo = static_cast(bits & 0xFFFFFFFFull); + const u32 hi = static_cast(bits >> 32); + return { + .lo = std::bit_cast(lo), + .hi = std::bit_cast(hi), + }; + }; + + const auto flip_sign_bit = [](float v) -> float { + u32 bits = std::bit_cast(v); + bits ^= 0x80000000u; + return std::bit_cast(bits); + }; + + const auto ordered_lt = [](float a, float b) -> bool { + return !std::isnan(a) && !std::isnan(b) && (a < b); + }; + + const auto maxss = [](float a, float b) -> float { + if (std::isnan(a) || std::isnan(b)) { + return b; + } + if (a > b) { + return a; + } + if (a < b) { + return b; + } + if (a == 0.0f && b == 0.0f) { + const bool a_neg = std::signbit(a); + const bool b_neg = std::signbit(b); + if (a_neg != b_neg) { + return a_neg ? b : a; + } + } + return a; + }; + + for (int n = entry_count; n != 0; --n) { + const u32 font_id = *entry_indices; + ++entry_indices; + if (static_cast(font_id) < 0) { + continue; + } + + float fontset_scale_factor = 1.0f; + int fontset_shift_value = 0; + if (fontset_record && + fontset_record->magic == Libraries::Font::Internal::FontSetSelector::kMagic) { + if (auto* st = Internal::TryGetState(fontHandle)) { + if (st->system_requested) { + if (font_id == st->system_font_id) { + fontset_scale_factor = st->system_font_scale_factor; + fontset_shift_value = st->system_font_shift_value; + } else { + for (const auto& fb : st->system_fallback_faces) { + if (fb.font_id == font_id) { + fontset_scale_factor = fb.scale_factor; + fontset_shift_value = fb.shift_value; + break; + } + } + } + } + } + } else { + (void)fontset_record; + } + + void* head = nullptr; + u32 lock_word = 0; + u8* entry_u8 = AcquireFontCtxEntry(ctx, font_id, mode_low, &head, &lock_word); + auto* entry = entry_u8 ? reinterpret_cast(entry_u8) : nullptr; + if (!entry) { + return fail_return(); + } + + FontObj* match = nullptr; + for (auto* node = static_cast(head); node != nullptr; node = node->next) { + if (node->sub_font_index == sub_font_index) { + match = node; + break; + } + } + + s32 rc = ORBIS_FONT_ERROR_FATAL; + HorizontalLayoutBlocks layout_tmp{}; + if (((lock_word & 0x40000000u) != 0) && ((lock_word & 0x0FFFFFFFu) != 0) && match) { + float scale_x = 0.0f; + float scale_y = 0.0f; + { + scale_x = fontset_scale_factor * style->scale_w; + scale_y = fontset_scale_factor * style->scale_h; + } + + match->font_handle = fontHandle; + match->shift_units_x = 0; + match->shift_units_y = fontset_shift_value; + + if (style->scale_unit == 0) { + rc = set_char_default_dpi(match, scale_x, scale_y, &scale_x, &scale_y); + } else { + rc = set_char_with_dpi(match, style->dpi_x, style->dpi_y, scale_x, scale_y, + &scale_x, &scale_y); + } + if (rc == ORBIS_OK) { + UpdateFtFontObjShiftCache(reinterpret_cast(match)); + rc = compute_layout(match, style_state_block, LayoutWordsBytes(layout_tmp.words())); + } + } + + u32* lock_word_ptr = nullptr; + if (mode_low == 3) { + if ((lock_word & 0x0FFFFFFFu) == 0) { + entry->obj_mode3 = nullptr; + } + lock_word_ptr = &entry->lock_mode3; + } else if (mode_low == 1) { + if ((lock_word & 0x0FFFFFFFu) == 0) { + entry->obj_mode1 = nullptr; + } + lock_word_ptr = &entry->lock_mode1; + } else { + if ((lock_word & 0x0FFFFFFFu) == 0) { + entry->obj_mode2 = nullptr; + } + lock_word_ptr = &entry->lock_mode2; + } + + if (((lock_word & 0x0FFFFFFFu) == 0) && entry->obj_mode1 == nullptr && + entry->obj_mode3 == nullptr && entry->obj_mode2 == nullptr) { + entry->unique_id = 0; + entry->active = 0; + } + *lock_word_ptr = lock_word & 0x7FFFFFFFu; + + if (rc != ORBIS_OK) { + return fail_return(); + } + + const auto layout = layout_tmp.values(); + const float line_advance = layout.line_advance; + const float baseline = layout.baseline; + const float effect_h = layout.effect_height; + const u64 x_bounds_bits = layout.x_bounds_bits; + const u64 i8_adj_u13_bits = layout.i8_adj_u13_bits; + + const F32x2 prev_u13_i8 = acc_u13_i8; + const F32x2 prev_bounds = acc_x_bounds; + + const float prev_baseline_max = baseline_max; + const float prev_effect_for_baseline = effect_for_baseline; + const bool baseline_update = ordered_lt(prev_baseline_max, baseline); + baseline_max = maxss(baseline, prev_baseline_max); + effect_for_baseline = baseline_update ? effect_h : prev_effect_for_baseline; + + const float prev_delta_max = delta_max; + const float prev_effect_for_delta = effect_for_delta; + const float new_delta = line_advance - baseline; + const bool delta_update = ordered_lt(prev_delta_max, new_delta); + delta_max = maxss(new_delta, prev_delta_max); + effect_for_delta = delta_update ? effect_h : prev_effect_for_delta; + + const F32x2 v_i8_adj_u13 = load_f32x2_from_u64(i8_adj_u13_bits); + const F32x2 v_x_bounds = load_f32x2_from_u64(x_bounds_bits); + + const F32x2 perm = {.lo = v_i8_adj_u13.hi, .hi = v_i8_adj_u13.lo}; + const F32x2 diff = {.lo = v_x_bounds.lo - perm.lo, .hi = v_x_bounds.hi - perm.hi}; + const F32x2 sum = { + .lo = prev_u13_i8.lo + prev_bounds.lo, + .hi = prev_u13_i8.hi + prev_bounds.hi, + }; + + const F32x2 inserted = {.lo = flip_sign_bit(v_i8_adj_u13.hi), .hi = v_i8_adj_u13.lo}; + + const bool mask_lo = ordered_lt(diff.lo, sum.lo); + const bool mask_hi = ordered_lt(diff.hi, sum.hi); + if (mask_lo) { + acc_x_bounds.lo = v_x_bounds.lo; + acc_u13_i8.lo = inserted.lo; + } + if (mask_hi) { + acc_x_bounds.hi = v_x_bounds.hi; + acc_u13_i8.hi = inserted.hi; + } + } + + if (!out_words) { + return fail_return(); + } + + const float line_h = baseline_max + delta_max; + const float effect_h = maxss(effect_for_baseline, effect_for_delta); + + if ((reinterpret_cast(out_words) & (alignof(HorizontalLayoutBlocks) - 1)) == + 0) { + auto* out_blocks = reinterpret_cast(out_words); + out_blocks->set_baseline(baseline_max); + out_blocks->set_line_advance(line_h); + out_blocks->set_x_bounds(acc_x_bounds.lo, acc_x_bounds.hi); + out_blocks->set_effect_height(effect_h); + out_blocks->set_i8_adj_u13_lanes(acc_u13_i8.lo, acc_u13_i8.hi); + } else { + auto out = HorizontalLayoutBlocksIo{out_words}.fields(); + out.baseline = baseline_max; + out.line_advance = line_h; + out.x_bound_0 = acc_x_bounds.lo; + out.x_bound_1 = acc_x_bounds.hi; + out.effect_height = effect_h; + out.u13_i8_lane0 = acc_u13_i8.lo; + out.u13_i8_lane1 = acc_u13_i8.hi; + } + + return ORBIS_OK; +} + +s32 ComputeVerticalLayoutBlocks(OrbisFontHandle fontHandle, const void* style_state_block, + u8 (*out_words)[16]) { + auto* font = GetNativeFont(fontHandle); + auto* lib = font ? reinterpret_cast(font->library) : nullptr; + if (!lib) { + if (out_words) { + std::memset(out_words[0], 0, 16); + std::memset(out_words[1] + 4, 0, 8); + std::memset(out_words[1] + 0x0C, 0, 4); + } + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + const void* fontset_record = font->open_info.fontset_record; + + const u32 mode_low = static_cast(font->flags) & 0x0Fu; + + const u32* font_ids = nullptr; + int entry_count = 0; + u32 rc = ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + + if (!fontset_record) { + entry_count = 1; + font_ids = &font->open_info.ctx_entry_index; + rc = ORBIS_OK; + } else { + entry_count = + *reinterpret_cast(reinterpret_cast(fontset_record) + 0x20); + font_ids = reinterpret_cast(reinterpret_cast(fontset_record) + 0x24); + rc = (entry_count != 0) ? ORBIS_OK : ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + u8* ctx = fontset_record ? static_cast(lib->sysfonts_ctx) + : static_cast(lib->external_fonts_ctx); + if (!ctx) { + if (out_words) { + std::memset(out_words[0], 0, 16); + std::memset(out_words[1] + 4, 0, 8); + std::memset(out_words[1] + 0x0C, 0, 4); + } + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + if (static_cast(rc) != ORBIS_OK) { + if (out_words) { + std::memset(out_words[0], 0, 16); + std::memset(out_words[1] + 4, 0, 8); + std::memset(out_words[1] + 0x0C, 0, 4); + } + return static_cast(rc); + } + + float acc_neg_local64_max = 0.0f; + float acc_sum_max = 0.0f; + float assoc_for_neg_local64_max = 0.0f; + float assoc_for_sum_max = 0.0f; + float acc_local60_max = 0.0f; + float acc_local5c_max = 0.0f; + float acc_diff_min = 0.0f; + float acc_temp_min = 0.0f; + + const SysDriver* driver = static_cast(lib->sys_driver); + if (!driver) { + if (out_words) { + std::memset(out_words[0], 0, 16); + std::memset(out_words[1] + 4, 0, 8); + std::memset(out_words[1] + 0x0C, 0, 4); + } + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + for (int i = 0; i < entry_count; ++i) { + const u32 font_id = font_ids ? font_ids[i] : 0u; + if (static_cast(font_id) < 0) { + continue; + } + + float scale_factor = 1.0f; + s32 shift_value = 0; + if (fontset_record && static_cast(fontset_record)->magic == + Libraries::Font::Internal::FontSetSelector::kMagic) { + if (auto* st = Internal::TryGetState(fontHandle)) { + if (st->system_requested) { + if (font_id == st->system_font_id) { + scale_factor = st->system_font_scale_factor; + shift_value = st->system_font_shift_value; + } else { + for (const auto& fb : st->system_fallback_faces) { + if (fb.font_id == font_id) { + scale_factor = fb.scale_factor; + shift_value = fb.shift_value; + break; + } + } + } + } + } + } else if (fontset_record) { + const auto desc = GetSysFontDesc(font_id); + scale_factor = desc.scale_factor; + shift_value = desc.shift_value; + } + + void* head = nullptr; + u32 lock_word = 0; + u8* entry_u8 = AcquireFontCtxEntry(ctx, font_id, mode_low, &head, &lock_word); + auto* entry = entry_u8 ? reinterpret_cast(entry_u8) : nullptr; + + const u32 sub_font_index = font->open_info.sub_font_index; + auto* font_obj = static_cast(head); + while (font_obj && font_obj->sub_font_index != sub_font_index) { + font_obj = font_obj->next; + } + + if (!entry || !font_obj) { + rc = ORBIS_FONT_ERROR_FATAL; + break; + } + + rc = ORBIS_FONT_ERROR_FATAL; + if (((lock_word & 0x40000000u) != 0) && ((lock_word & 0x0FFFFFFFu) != 0)) { + const auto* style = static_cast(style_state_block); + const float base_scale_x = style ? style->scale_w : 0.0f; + const float base_scale_y = style ? style->scale_h : 0.0f; + + float scale_x = scale_factor * base_scale_x; + float scale_y = scale_factor * base_scale_y; + + font_obj->font_handle = fontHandle; + font_obj->shift_units_x = 0; + font_obj->shift_units_y = shift_value; + + s32 call_rc = ORBIS_FONT_ERROR_FATAL; + if (style && style->scale_unit == 0) { + call_rc = driver->set_char_default_dpi + ? driver->set_char_default_dpi(font_obj, scale_x, scale_y, &scale_x, + &scale_y) + : ORBIS_FONT_ERROR_FATAL; + } else { + call_rc = driver->set_char_with_dpi + ? driver->set_char_with_dpi(font_obj, style ? style->dpi_x : 0u, + style ? style->dpi_y : 0u, scale_x, + scale_y, &scale_x, &scale_y) + : ORBIS_FONT_ERROR_FATAL; + } + + VerticalLayoutAltBlocks layout_alt{}; + if (call_rc == ORBIS_OK) { + UpdateFtFontObjShiftCache(reinterpret_cast(font_obj)); + call_rc = driver->compute_layout_alt + ? driver->compute_layout_alt(font_obj, style_state_block, + LayoutWordsBytes(layout_alt.words())) + : ORBIS_FONT_ERROR_FATAL; + } + + if (call_rc == ORBIS_OK) { + const auto v = layout_alt.values(); + const float primary_0x00 = v.metrics.unknown_0x00; + const float baseline_offset_x_candidate = v.metrics.baseline_offset_x_candidate; + const float primary_0x08 = v.metrics.unknown_0x08; + const float primary_0x0C = v.metrics.unknown_0x0C; + const float decoration_span_candidate = v.extras.decoration_span_candidate; + const float extra_0x08 = v.extras.unknown_0x08; + const float extra_0x0C = v.extras.unknown_0x0C; + + const float neg_baseline_offset_x_candidate = -baseline_offset_x_candidate; + const float sum_primary_0x00_and_baseline_offset_x_candidate = + baseline_offset_x_candidate + primary_0x00; + + if (acc_neg_local64_max < neg_baseline_offset_x_candidate) { + assoc_for_neg_local64_max = decoration_span_candidate; + } + acc_neg_local64_max = + std::max(acc_neg_local64_max, neg_baseline_offset_x_candidate); + + if (acc_sum_max < sum_primary_0x00_and_baseline_offset_x_candidate) { + assoc_for_sum_max = decoration_span_candidate; + } + acc_sum_max = + std::max(acc_sum_max, sum_primary_0x00_and_baseline_offset_x_candidate); + + const float diff = extra_0x08 - baseline_offset_x_candidate; + acc_diff_min = std::min(diff, acc_diff_min); + + acc_local60_max = std::max(primary_0x08, acc_local60_max); + + const float sum2 = sum_primary_0x00_and_baseline_offset_x_candidate + extra_0x0C; + acc_temp_min = std::min(sum2, acc_diff_min); + + acc_local5c_max = std::max(primary_0x0C, acc_local5c_max); + rc = ORBIS_OK; + } else { + rc = call_rc; + } + } + + const u32 low_bits = lock_word & 0x0FFFFFFFu; + u32* lock_word_store = nullptr; + if (mode_low == 3) { + if (low_bits == 0) { + entry->obj_mode3 = nullptr; + } + lock_word_store = &entry->lock_mode3; + } else if (mode_low == 1) { + if (low_bits == 0) { + entry->obj_mode1 = nullptr; + } + lock_word_store = &entry->lock_mode1; + } else { + if (low_bits == 0) { + entry->obj_mode2 = nullptr; + } + lock_word_store = &entry->lock_mode2; + } + + if ((low_bits == 0) && entry->obj_mode1 == nullptr && entry->obj_mode3 == nullptr && + entry->obj_mode2 == nullptr) { + entry->unique_id = 0; + entry->active = 0; + } + if (lock_word_store) { + *lock_word_store = lock_word & 0x7FFFFFFFu; + } + + if (static_cast(rc) != ORBIS_OK) { + break; + } + } + + if (static_cast(rc) != ORBIS_OK) { + if (out_words) { + std::memset(out_words[0], 0, 16); + std::memset(out_words[1] + 4, 0, 8); + std::memset(out_words[1] + 0x0C, 0, 4); + } + return static_cast(rc); + } + + if (out_words) { + const float column_advance = acc_neg_local64_max + acc_sum_max; + const float baseline_offset_x = -acc_neg_local64_max; + const float decoration_span = std::max(assoc_for_sum_max, assoc_for_neg_local64_max); + const float unknown_decoration_0x08 = acc_temp_min - acc_neg_local64_max; + const float unknown_decoration_0x0C = -acc_sum_max; + + if ((reinterpret_cast(out_words) & (alignof(VerticalLayoutBlocks) - 1)) == + 0) { + auto* out_blocks = reinterpret_cast(out_words); + out_blocks->set_column_advance(column_advance); + out_blocks->set_baseline_offset_x(baseline_offset_x); + out_blocks->set_unknown_metrics_0x08(acc_local60_max); + out_blocks->set_unknown_metrics_0x0C(acc_local5c_max); + out_blocks->set_decoration_span(decoration_span); + out_blocks->set_unknown_decoration_0x08(unknown_decoration_0x08); + out_blocks->set_unknown_decoration_0x0C(unknown_decoration_0x0C); + } else { + auto out = VerticalLayoutBlocksIo{out_words}.fields(); + out.column_advance = column_advance; + out.baseline_offset_x = baseline_offset_x; + out.unknown_metrics_0x08 = acc_local60_max; + out.unknown_metrics_0x0C = acc_local5c_max; + out.decoration_span = decoration_span; + out.unknown_decoration_0x08 = unknown_decoration_0x08; + out.unknown_decoration_0x0C = unknown_decoration_0x0C; + } + } + return ORBIS_OK; +} + +s32 GetCharGlyphMetrics(OrbisFontHandle fontHandle, u32 code, OrbisFontGlyphMetrics* metrics, + bool use_cached_style) { + auto clear_metrics = [&] { + if (metrics) { + *metrics = {}; + } + }; + + auto* font = GetNativeFont(fontHandle); + if (!font || font->magic != 0x0F02) { + clear_metrics(); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + if (code == 0) { + clear_metrics(); + return ORBIS_FONT_ERROR_NO_SUPPORT_CODE; + } + if (!metrics) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + + u32 prev_font_lock = 0; + if (!AcquireFontLock(font, prev_font_lock)) { + clear_metrics(); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + u32 prev_cached_lock = 0; + if (use_cached_style) { + if (!AcquireCachedStyleLock(font, prev_cached_lock)) { + ReleaseFontLock(font, prev_font_lock); + clear_metrics(); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + } + + auto* st = TryGetState(fontHandle); + if (!st) { + if (use_cached_style) { + font->cached_style.cache_lock_word = prev_cached_lock; + } + font->lock_word = prev_font_lock; + clear_metrics(); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + if (use_cached_style && font->renderer_binding.renderer == nullptr) { + font->cached_style.cache_lock_word = prev_cached_lock; + font->lock_word = prev_font_lock; + clear_metrics(); + return ORBIS_FONT_ERROR_NOT_BOUND_RENDERER; + } + + const void* style_state_block = use_cached_style ? static_cast(&font->cached_style) + : static_cast(font->style_frame); + float scale_w = 0.0f; + float scale_h = 0.0f; + const s32 scale_rc = StyleStateGetScalePixel(style_state_block, &scale_w, &scale_h); + if (scale_rc != ORBIS_OK) { + if (use_cached_style) { + font->cached_style.cache_lock_word = prev_cached_lock; + } + font->lock_word = prev_font_lock; + clear_metrics(); + return scale_rc; + } + + FT_Face resolved_face = nullptr; + if (st->ext_face_ready) { + resolved_face = st->ext_ft_face; + } else { + bool system_attached = false; + (void)ReportSystemFaceRequest(*st, fontHandle, system_attached); + if (system_attached && st->ext_face_ready) { + resolved_face = st->ext_ft_face; + } + } + + if (!resolved_face) { + if (use_cached_style) { + font->cached_style.cache_lock_word = prev_cached_lock; + } + font->lock_word = prev_font_lock; + clear_metrics(); + return ORBIS_FONT_ERROR_NO_SUPPORT_FUNCTION; + } + + u32 resolved_code = code; + u32 resolved_font_id = 0; + float resolved_scale_factor = 1.0f; + const auto* fontset_record = + static_cast(font->open_info.fontset_record); + const bool is_internal_fontset_record = + fontset_record && + fontset_record->magic == Libraries::Font::Internal::FontSetSelector::kMagic; + if (font->open_info.fontset_record) { + u32 mapped_font_id = 0; + resolved_code = + ResolveSysFontCodepoint(font->open_info.fontset_record, static_cast(code), + font->open_info.fontset_flags, &mapped_font_id); + if (resolved_code == 0) { + if (use_cached_style) { + font->cached_style.cache_lock_word = prev_cached_lock; + } + font->lock_word = prev_font_lock; + clear_metrics(); + return ORBIS_FONT_ERROR_NO_SUPPORT_CODE; + } + + resolved_font_id = mapped_font_id; + resolved_scale_factor = st->system_font_scale_factor; + if (is_internal_fontset_record) { + auto* lib = static_cast(font->library); + auto* ctx = lib ? static_cast(lib->sysfonts_ctx) : nullptr; + const u32 mode_low = static_cast(font->flags) & 0x0Fu; + if (ctx && (mode_low == 1 || mode_low == 2 || mode_low == 3)) { + void* head = nullptr; + u32 lock_word = 0; + u8* entry_u8 = + AcquireFontCtxEntry(ctx, mapped_font_id, mode_low, &head, &lock_word); + auto* entry = entry_u8 ? reinterpret_cast(entry_u8) : nullptr; + auto* node = static_cast(head); + const u32 sub_font_index = font->open_info.sub_font_index; + while (node) { + if (node->sub_font_index == sub_font_index) { + break; + } + node = node->next; + } + resolved_face = node ? static_cast(node->ft_face) : nullptr; + if (entry) { + if (mode_low == 3) { + entry->lock_mode3 = lock_word & 0x7fffffffu; + } else if (mode_low == 2) { + entry->lock_mode2 = lock_word & 0x7fffffffu; + } else { + entry->lock_mode1 = lock_word & 0x7fffffffu; + } + } + } + } else if (st->system_requested) { + if (mapped_font_id == st->system_font_id) { + resolved_face = st->ext_ft_face; + } else { + resolved_face = nullptr; + for (const auto& fb : st->system_fallback_faces) { + if (fb.ready && fb.ft_face && fb.font_id == mapped_font_id) { + resolved_face = fb.ft_face; + resolved_scale_factor = fb.scale_factor; + break; + } + } + } + } + if (!resolved_face) { + if (use_cached_style) { + font->cached_style.cache_lock_word = prev_cached_lock; + } + font->lock_word = prev_font_lock; + clear_metrics(); + return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH; + } + } + + FT_UInt resolved_glyph_index = + FT_Get_Char_Index(resolved_face, static_cast(resolved_code)); + if (resolved_glyph_index == 0) { + if (st->ext_face_ready && st->ext_ft_face && resolved_face != st->ext_ft_face) { + const FT_UInt gi = + FT_Get_Char_Index(st->ext_ft_face, static_cast(resolved_code)); + if (gi != 0) { + resolved_face = st->ext_ft_face; + resolved_glyph_index = gi; + resolved_scale_factor = st->system_font_scale_factor; + } + } + } + if (resolved_glyph_index == 0 && !font->open_info.fontset_record) { + for (const auto& fb : st->system_fallback_faces) { + if (!fb.ready || !fb.ft_face) { + continue; + } + const FT_UInt gi = FT_Get_Char_Index(fb.ft_face, static_cast(resolved_code)); + if (gi == 0) { + continue; + } + resolved_face = fb.ft_face; + resolved_glyph_index = gi; + resolved_scale_factor = fb.scale_factor; + break; + } + } + + if (resolved_glyph_index == 0) { + LogGetCharGlyphMetricsFailOnce("no_glyph", ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH, 0, + font->open_info.fontset_record != nullptr, code, + resolved_glyph_index, resolved_face, scale_w, scale_h); + if (use_cached_style) { + font->cached_style.cache_lock_word = prev_cached_lock; + } + font->lock_word = prev_font_lock; + clear_metrics(); + return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH; + } + + float range_scale = 1.0f; + s32 shift_x_units = 0; + s32 shift_y_units = 0; + if (font->open_info.fontset_record && !is_internal_fontset_record) { + if (st->system_requested) { + if (resolved_font_id == st->system_font_id) { + shift_y_units = st->system_font_shift_value; + } else { + for (const auto& fb : st->system_fallback_faces) { + if (fb.ready && fb.ft_face && fb.font_id == resolved_font_id) { + shift_y_units = fb.shift_value; + break; + } + } + } + } + if (shift_y_units == 0) { + shift_y_units = GetSysFontDesc(resolved_font_id).shift_value; + } + if (const std::uint8_t* range_rec = + FindSysFontRangeRecord(resolved_font_id, resolved_code)) { + alignas(4) SysFontRangeRecord rec{}; + std::memcpy(&rec, range_rec, sizeof(rec)); + const s16 range_shift_x = rec.shift_x; + const s16 range_shift_y = rec.shift_y; + range_scale = rec.scale_mul; + (void)rec.reserved_0x10; + (void)rec.reserved_0x14; + (void)rec.reserved_0x18; + + shift_x_units = static_cast(range_shift_x); + shift_y_units += static_cast(range_shift_y); + } + if (range_scale == 0.0f) { + range_scale = 1.0f; + } + } + + const float scaled_w = scale_w * resolved_scale_factor * range_scale; + const float scaled_h = scale_h * resolved_scale_factor * range_scale; + + const auto char_w = static_cast(static_cast(scaled_w * 64.0f)); + const auto char_h = static_cast(static_cast(scaled_h * 64.0f)); + if (FT_Set_Char_Size(resolved_face, char_w, char_h, 72, 72) != 0) { + if (use_cached_style) { + font->cached_style.cache_lock_word = prev_cached_lock; + } + font->lock_word = prev_font_lock; + clear_metrics(); + return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH; + } + + if (shift_x_units != 0 || shift_y_units != 0) { + if (!resolved_face->size) { + if (use_cached_style) { + font->cached_style.cache_lock_word = prev_cached_lock; + } + font->lock_word = prev_font_lock; + clear_metrics(); + return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH; + } + const long x_scale = static_cast(resolved_face->size->metrics.x_scale); + const long y_scale = static_cast(resolved_face->size->metrics.y_scale); + + const auto round_fixed_mul = [](long fixed_16_16, s32 value) -> s32 { + const long long prod = + static_cast(fixed_16_16) * static_cast(value); + const long long sign_adj = + (static_cast(~static_cast(value)) >> 63) * -0x10000LL; + const long long base = sign_adj + prod; + long long tmp = base - 0x8000LL; + if (tmp < 0) { + tmp = base + 0x7FFFLL; + } + return static_cast(static_cast(tmp) >> 16); + }; + + FT_Vector delta{}; + delta.x = static_cast(round_fixed_mul(x_scale, shift_x_units)); + delta.y = static_cast(round_fixed_mul(y_scale, shift_y_units)); + FT_Set_Transform(resolved_face, nullptr, &delta); + } else { + FT_Set_Transform(resolved_face, nullptr, nullptr); + } + + constexpr FT_Int32 kFtLoadFlagsBase = + static_cast(FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP | FT_LOAD_VERTICAL_LAYOUT); + + if (FT_Load_Glyph(resolved_face, resolved_glyph_index, kFtLoadFlagsBase) != 0) { + FT_Set_Transform(resolved_face, nullptr, nullptr); + if (use_cached_style) { + font->cached_style.cache_lock_word = prev_cached_lock; + } + font->lock_word = prev_font_lock; + clear_metrics(); + return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH; + } + FT_Set_Transform(resolved_face, nullptr, nullptr); + + const FT_GlyphSlot slot = resolved_face->glyph; + const float bearing_x = static_cast(slot->metrics.horiBearingX) / 64.0f; + const float bearing_y = static_cast(slot->metrics.horiBearingY) / 64.0f; + const float advance = static_cast(slot->metrics.horiAdvance) / 64.0f; + const float width_px = static_cast(slot->metrics.width) / 64.0f; + const float height_px = static_cast(slot->metrics.height) / 64.0f; + + metrics->width = width_px; + metrics->height = height_px; + metrics->Horizontal.bearingX = bearing_x; + metrics->Horizontal.bearingY = bearing_y; + metrics->Horizontal.advance = advance; + metrics->Vertical.bearingX = 0.0f; + metrics->Vertical.bearingY = 0.0f; + metrics->Vertical.advance = 0.0f; + + if (use_cached_style) { + font->cached_style.cache_lock_word = prev_cached_lock; + } + font->lock_word = prev_font_lock; + return ORBIS_OK; +} + +static StyleFrameScaleState ResolveStyleFrameScaleFromCachedStyle(const OrbisFontStyleFrame* style, + float base_scale_w, + float base_scale_h, + u32 base_dpi_x, u32 base_dpi_y) { + StyleFrameScaleState resolved{ + .scale_w = base_scale_w, + .scale_h = base_scale_h, + .dpi_x = base_dpi_x, + .dpi_y = base_dpi_y, + .scale_overridden = false, + .dpi_overridden = false, + }; + if (!style || style->magic != kStyleFrameMagic) { + return resolved; + } + if (style->hDpi != 0) { + resolved.dpi_x = style->hDpi; + resolved.dpi_overridden = true; + } + if (style->vDpi != 0) { + resolved.dpi_y = style->vDpi; + resolved.dpi_overridden = true; + } + if ((style->flags1 & kStyleFrameFlagScale) == 0) { + return resolved; + } + resolved.scale_overridden = true; + + const bool unit_is_pixel = (style->scaleUnit == 0); + if (unit_is_pixel) { + resolved.scale_w = style->scalePixelW; + resolved.scale_h = style->scalePixelH; + } else { + const u32 dpi_x = style->hDpi; + const u32 dpi_y = style->vDpi; + resolved.scale_w = dpi_x ? PointsToPixels(style->scalePixelW, dpi_x) : style->scalePixelW; + resolved.scale_h = dpi_y ? PointsToPixels(style->scalePixelH, dpi_y) : style->scalePixelH; + } + return resolved; +} + +namespace { + +static inline RenderSurfaceSystemUse* GetSurfaceSystemUse( + Libraries::Font::OrbisFontRenderSurface* surf) { + return surf ? reinterpret_cast(surf->reserved_q) : nullptr; +} +static inline const RenderSurfaceSystemUse* GetSurfaceSystemUse( + const Libraries::Font::OrbisFontRenderSurface* surf) { + return surf ? reinterpret_cast(surf->reserved_q) : nullptr; +} + +static const StyleStateBlock* ResolveSurfaceStyleState( + const StyleStateBlock* cached_style_state, const Libraries::Font::OrbisFontRenderSurface* surf, + const RenderSurfaceSystemUse* sys, StyleStateBlock& tmp_out) { + if (!cached_style_state) { + return nullptr; + } + if (!surf || (surf->styleFlag & 0x1) == 0 || !sys || !ValidateStyleFramePtr(sys->styleframe)) { + return cached_style_state; + } + + const auto* frame = sys->styleframe; + tmp_out = *cached_style_state; + + const u8 flags1 = frame->flags1; + if ((flags1 & kStyleFrameFlagScale) != 0) { + tmp_out.scale_unit = frame->scaleUnit; + tmp_out.scale_w = frame->scalePixelW; + tmp_out.scale_h = frame->scalePixelH; + tmp_out.dpi_x = frame->hDpi ? frame->hDpi : 0x48; + tmp_out.dpi_y = frame->vDpi ? frame->vDpi : 0x48; + } else { + const u32 want_x = (frame->hDpi == 0) ? tmp_out.dpi_x : frame->hDpi; + const u32 want_y = (frame->vDpi == 0) ? tmp_out.dpi_y : frame->vDpi; + tmp_out.dpi_x = (want_x != 0) ? 0x48 : 0; + tmp_out.dpi_y = (want_y != 0) ? 0x48 : 0; + } + + if ((flags1 & kStyleFrameFlagSlant) != 0) { + tmp_out.slant_ratio = frame->slantRatio; + } + if ((flags1 & kStyleFrameFlagWeight) != 0) { + tmp_out.effect_weight_x = frame->effectWeightX; + tmp_out.effect_weight_y = frame->effectWeightY; + } + return &tmp_out; +} + +static s32 RenderGlyphIndexToSurface(FontObj& font_obj, u32 glyph_index, + Libraries::Font::OrbisFontRenderSurface* surf, float x, + float y, Libraries::Font::OrbisFontGlyphMetrics* metrics, + Libraries::Font::OrbisFontRenderOutput* result) { + ClearRenderOutputs(metrics, result); + if (!surf || !metrics || !result) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + if (!surf->buffer || surf->width <= 0 || surf->height <= 0 || surf->widthByte <= 0 || + surf->pixelSizeByte <= 0) { + return ORBIS_FONT_ERROR_NO_SUPPORT_SURFACE; + } + + const int bpp = static_cast(surf->pixelSizeByte); + if (bpp != 1 && bpp != 4) { + return ORBIS_FONT_ERROR_NO_SUPPORT_SURFACE; + } + + FT_Face face = static_cast(font_obj.ft_face); + if (!face || !face->size) { + return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH; + } + + const float frac_x = x - std::floor(x); + const float frac_y = y - std::floor(y); + FT_Vector delta{}; + delta.x = static_cast(static_cast(frac_x * 64.0f)); + delta.y = static_cast(-static_cast(frac_y * 64.0f)); + + if ((font_obj.shift_units_x != 0 || font_obj.shift_units_y != 0) && face->size) { + const long x_scale = static_cast(face->size->metrics.x_scale); + const long y_scale = static_cast(face->size->metrics.y_scale); + + const auto round_fixed_mul = [](long fixed_16_16, s32 value) -> s32 { + const long long prod = + static_cast(fixed_16_16) * static_cast(value); + const long long sign_adj = + (static_cast(~static_cast(value)) >> 63) * -0x10000LL; + const long long base = sign_adj + prod; + long long tmp = base - 0x8000LL; + if (tmp < 0) { + tmp = base + 0x7FFFLL; + } + return static_cast(static_cast(tmp) >> 16); + }; + + delta.x += static_cast(round_fixed_mul(x_scale, font_obj.shift_units_x)); + delta.y += static_cast(round_fixed_mul(y_scale, font_obj.shift_units_y)); + } + + FT_Set_Transform(face, nullptr, &delta); + + constexpr FT_Int32 kFtLoadFlagsRender = static_cast( + FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP | FT_LOAD_VERTICAL_LAYOUT | FT_LOAD_RENDER); + if (FT_Load_Glyph(face, static_cast(glyph_index), kFtLoadFlagsRender) != 0) { + FT_Set_Transform(face, nullptr, nullptr); + return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH; + } + FT_Set_Transform(face, nullptr, nullptr); + + const FT_GlyphSlot slot = face->glyph; + if (!slot) { + return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH; + } + + const int glyph_w = static_cast(slot->bitmap.width); + const int glyph_h = static_cast(slot->bitmap.rows); + const int x0 = static_cast(slot->bitmap_left); + const int y0 = -static_cast(slot->bitmap_top); + + const float bearing_x = static_cast(slot->metrics.horiBearingX) / 64.0f; + const float bearing_y = static_cast(slot->metrics.horiBearingY) / 64.0f; + const float advance = static_cast(slot->metrics.horiAdvance) / 64.0f; + const float width_px = static_cast(slot->metrics.width) / 64.0f; + const float height_px = static_cast(slot->metrics.height) / 64.0f; + + metrics->width = width_px; + metrics->height = height_px; + metrics->Horizontal.bearingX = bearing_x; + metrics->Horizontal.bearingY = bearing_y; + metrics->Horizontal.advance = advance; + metrics->Vertical.bearingX = 0.0f; + metrics->Vertical.bearingY = 0.0f; + metrics->Vertical.advance = 0.0f; + + std::vector glyph_bitmap; + glyph_bitmap.resize(static_cast(glyph_w) * static_cast(glyph_h)); + if (glyph_w > 0 && glyph_h > 0) { + const int pitch = static_cast(slot->bitmap.pitch); + const unsigned char* src = reinterpret_cast(slot->bitmap.buffer); + if (slot->bitmap.pixel_mode == FT_PIXEL_MODE_MONO) { + for (int row = 0; row < glyph_h; ++row) { + const unsigned char* src_row = src + static_cast(row) * pitch; + unsigned char* dst_row = + glyph_bitmap.data() + static_cast(row) * glyph_w; + for (int col = 0; col < glyph_w; ++col) { + const unsigned char byte = src_row[col >> 3]; + const unsigned char bit = static_cast(0x80u >> (col & 7)); + dst_row[col] = (byte & bit) ? 0xFFu : 0x00u; + } + } + } else { + for (int row = 0; row < glyph_h; ++row) { + const unsigned char* src_row = src + static_cast(row) * pitch; + unsigned char* dst_row = + glyph_bitmap.data() + static_cast(row) * glyph_w; + std::memcpy(dst_row, src_row, static_cast(glyph_w)); + } + } + } + + const int dest_x = static_cast(std::floor(x)) + x0; + const int dest_y = static_cast(std::floor(y)) + y0; + + const int surface_w = std::max(surf->width, 0); + const int surface_h = std::max(surf->height, 0); + const int clip_x0 = std::clamp(static_cast(surf->sc_x0), 0, surface_w); + const int clip_y0 = std::clamp(static_cast(surf->sc_y0), 0, surface_h); + const int clip_x1 = std::clamp(static_cast(surf->sc_x1), 0, surface_w); + const int clip_y1 = std::clamp(static_cast(surf->sc_y1), 0, surface_h); + + int update_x0 = dest_x; + int update_y0 = dest_y; + int update_w = 0; + int update_h = 0; + + if (glyph_w > 0 && glyph_h > 0 && clip_x1 > clip_x0 && clip_y1 > clip_y0) { + auto* dst_base = static_cast(surf->buffer); + const int start_row = std::max(dest_y, clip_y0); + const int end_row = std::min(dest_y + glyph_h, clip_y1); + const int start_col = std::max(dest_x, clip_x0); + const int end_col = std::min(dest_x + glyph_w, clip_x1); + + update_x0 = start_col; + update_y0 = start_row; + update_w = std::max(0, end_col - start_col); + update_h = std::max(0, end_row - start_row); + + for (int row = start_row; row < end_row; ++row) { + const int src_y = row - dest_y; + if (src_y < 0 || src_y >= glyph_h) { + continue; + } + const std::uint8_t* src_row = + glyph_bitmap.data() + static_cast(src_y) * glyph_w; + std::uint8_t* dst_row = dst_base + static_cast(row) * + static_cast(surf->widthByte); + for (int col = start_col; col < end_col; ++col) { + const int src_x = col - dest_x; + if (src_x < 0 || src_x >= glyph_w) { + continue; + } + const std::uint8_t cov = src_row[src_x]; + std::uint8_t* dst = dst_row + static_cast(col) * bpp; + if (bpp == 1) { + dst[0] = cov; + } else { + dst[0] = cov; + dst[1] = cov; + dst[2] = cov; + dst[3] = cov; + } + } + } + } + + result->stage = nullptr; + result->SurfaceImage.address = static_cast(surf->buffer); + result->SurfaceImage.widthByte = static_cast(surf->widthByte); + result->SurfaceImage.pixelSizeByte = static_cast(surf->pixelSizeByte); + result->SurfaceImage.pixelFormat = 0; + result->SurfaceImage.pad16 = 0; + result->UpdateRect.x = static_cast(std::max(update_x0, 0)); + result->UpdateRect.y = static_cast(std::max(update_y0, 0)); + result->UpdateRect.w = static_cast(std::max(update_w, 0)); + result->UpdateRect.h = static_cast(std::max(update_h, 0)); + result->SurfaceImage.address = + static_cast(surf->buffer) + + static_cast(result->UpdateRect.y) * static_cast(surf->widthByte) + + static_cast(result->UpdateRect.x) * static_cast(bpp); + + const auto floor_int = [](float v) -> int { + int i = static_cast(std::trunc(v)); + if (static_cast(i) > v) { + --i; + } + return i; + }; + const auto ceil_int = [](float v) -> int { + int i = static_cast(std::trunc(v)); + if (static_cast(i) < v) { + ++i; + } + return i; + }; + + const float left_f = x + metrics->Horizontal.bearingX; + const float top_f = y + metrics->Horizontal.bearingY; + const float right_f = left_f + metrics->width; + const float bottom_f = top_f - metrics->height; + + const int left_i = floor_int(left_f); + const int top_i = floor_int(top_f); + const int right_i = ceil_int(right_f); + const int bottom_i = floor_int(bottom_f); + + const float adv_f = x + metrics->Horizontal.advance; + const float adv_snapped = static_cast(floor_int(adv_f)) - x; + + result->ImageMetrics.bearingX = static_cast(left_i) - x; + result->ImageMetrics.bearingY = static_cast(top_i) - y; + result->ImageMetrics.advance = adv_snapped; + int stride_i = right_i + 1; + const float adjust = static_cast(right_i) - right_f; + const int tmp_i = floor_int(adv_f + adjust); + const int adv_trunc_i = static_cast(std::trunc(adv_f)); + if (adv_trunc_i == 0) { + stride_i = tmp_i; + } + if (stride_i < tmp_i) { + stride_i = tmp_i; + } + result->ImageMetrics.stride = static_cast(stride_i) - x; + result->ImageMetrics.width = static_cast(std::max(0, right_i - left_i)); + result->ImageMetrics.height = static_cast(std::max(0, top_i - bottom_i)); + return ORBIS_OK; +} + +static StyleFrameScaleState ResolveMergedStyleFrameScale( + const Libraries::Font::OrbisFontStyleFrame* base, + const Libraries::Font::OrbisFontStyleFrame* over, const FontState& st) { + StyleFrameScaleState resolved = ResolveStyleFrameScale(base, st); + if (!ValidateStyleFramePtr(over)) { + return resolved; + } + if ((over->flags1 & kStyleFrameFlagScale) != 0) { + resolved.scale_overridden = true; + const bool unit_is_pixel = (over->scaleUnit == 0); + const u32 dpi_x = over->hDpi; + const u32 dpi_y = over->vDpi; + resolved.scale_w = + unit_is_pixel ? over->scalePixelW + : (dpi_x ? PointsToPixels(over->scalePixelW, dpi_x) : over->scalePixelW); + resolved.scale_h = + unit_is_pixel ? over->scalePixelH + : (dpi_y ? PointsToPixels(over->scalePixelH, dpi_y) : over->scalePixelH); + } + if (!resolved.scale_overridden && st.scale_point_active) { + resolved.scale_w = PointsToPixels(st.scale_point_w, resolved.dpi_x); + resolved.scale_h = PointsToPixels(st.scale_point_h, resolved.dpi_y); + } + return resolved; +} + +} // namespace + +s32 RenderCharGlyphImageCore(Libraries::Font::OrbisFontHandle fontHandle, u32 code, + Libraries::Font::OrbisFontRenderSurface* surf, float x, float y, + Libraries::Font::OrbisFontGlyphMetrics* metrics, + Libraries::Font::OrbisFontRenderOutput* result) { + if (!fontHandle) { + ClearRenderOutputs(metrics, result); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + if (code == 0) { + ClearRenderOutputs(metrics, result); + return ORBIS_FONT_ERROR_NO_SUPPORT_CODE; + } + if (!surf || !metrics || !result) { + ClearRenderOutputs(metrics, result); + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + + auto* font = GetNativeFont(fontHandle); + if (!font || font->magic != 0x0F02) { + ClearRenderOutputs(metrics, result); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + if (font->renderer_binding.renderer == nullptr) { + ClearRenderOutputs(metrics, result); + return ORBIS_FONT_ERROR_NOT_BOUND_RENDERER; + } + + auto* lib = static_cast(font->library); + if (!lib || lib->magic != 0x0F01) { + ClearRenderOutputs(metrics, result); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + const auto* driver = reinterpret_cast(lib->sys_driver); + if (!driver || !driver->glyph_index || !driver->set_char_with_dpi || + !driver->set_char_default_dpi) { + ClearRenderOutputs(metrics, result); + return ORBIS_FONT_ERROR_FATAL; + } + + const auto mode_low = static_cast(font->flags) & 0x0Fu; + const u32 sub_font_index = font->open_info.sub_font_index; + + const auto* cached_style_state = reinterpret_cast(&font->cached_style); + StyleStateBlock surface_style_tmp{}; + const RenderSurfaceSystemUse* surf_sys = GetSurfaceSystemUse(surf); + const StyleStateBlock* style_state = + ResolveSurfaceStyleState(cached_style_state, surf, surf_sys, surface_style_tmp); + if (!style_state) { + ClearRenderOutputs(metrics, result); + return ORBIS_FONT_ERROR_FATAL; + } + + float scale_x_in = style_state->scale_w; + float scale_y_in = style_state->scale_h; + u32 dpi_x = style_state->dpi_x; + u32 dpi_y = style_state->dpi_y; + const u32 scale_unit = style_state->scale_unit; + + u32 mapped_code = code; + u32 font_id = font->open_info.ctx_entry_index; + std::uint8_t* ctx = static_cast(lib->external_fonts_ctx); + float sys_scale_factor = 1.0f; + s32 shift_x_units = 0; + s32 shift_y_units = 0; + + u32 special_case_font_id = 0xffffffffu; + if (font->open_info.fontset_record) { + const auto* fontset_record = + static_cast(font->open_info.fontset_record); + const bool is_internal_fontset_record = + fontset_record && + fontset_record->magic == Libraries::Font::Internal::FontSetSelector::kMagic; + ctx = static_cast(lib->sysfonts_ctx); + mapped_code = + ResolveSysFontCodepoint(font->open_info.fontset_record, static_cast(code), + font->open_info.fontset_flags, &font_id); + if (mapped_code == 0) { + ClearRenderOutputs(metrics, result); + return ORBIS_FONT_ERROR_NO_SUPPORT_CODE; + } + + if (is_internal_fontset_record) { + if (auto* st = Internal::TryGetState(fontHandle)) { + if (st->system_requested) { + if (font_id == st->system_font_id) { + sys_scale_factor = st->system_font_scale_factor; + shift_y_units = st->system_font_shift_value; + } else { + for (const auto& fb : st->system_fallback_faces) { + if (fb.font_id == font_id) { + sys_scale_factor = fb.scale_factor; + shift_y_units = fb.shift_value; + break; + } + } + } + } + } + } else { + const SysFontDesc desc = GetSysFontDesc(font_id); + sys_scale_factor = desc.scale_factor; + shift_y_units = desc.shift_value; + + float range_scale = 1.0f; + if (const std::uint8_t* range_rec = FindSysFontRangeRecord(font_id, mapped_code)) { + alignas(4) SysFontRangeRecord rec{}; + std::memcpy(&rec, range_rec, sizeof(rec)); + shift_x_units = static_cast(rec.shift_x); + shift_y_units += static_cast(rec.shift_y); + range_scale = rec.scale_mul; + } + if (range_scale != 0.0f) { + sys_scale_factor *= range_scale; + } + } + + if (font_id == 10) { + special_case_font_id = font_id; + } + } + + { + static std::mutex s_mutex; + static std::unordered_map s_counts; + std::scoped_lock lock(s_mutex); + int& count = s_counts[fontHandle]; + const bool force_diag = (code == 0x000Au) || (code == 0x000Du) || (code == 0x25B3u); + if (force_diag || count < 5) { + if (!force_diag) { + ++count; + } + LOG_DEBUG(Lib_Font, + "SysfontRender: handle={} code=U+{:04X} mapped=U+{:04X} font_id={} " + "scale_unit={} dpi_x={} dpi_y={} scale_w={} scale_h={} sys_scale_factor={} " + "shift_y_units={}", + static_cast(fontHandle), code, mapped_code, font_id, scale_unit, + dpi_x, dpi_y, scale_x_in, scale_y_in, sys_scale_factor, shift_y_units); + } + } + + void* font_obj_void = nullptr; + u32 lock_word = 0; + std::uint8_t* entry_u8 = AcquireFontCtxEntryAndSelectNode( + ctx, font_id, mode_low, sub_font_index, &font_obj_void, &lock_word); + auto* font_obj = static_cast(font_obj_void); + if (!entry_u8 || !font_obj) { + ClearRenderOutputs(metrics, result); + return ORBIS_FONT_ERROR_FATAL; + } + + s32 rc = ORBIS_FONT_ERROR_FATAL; + const auto cleanup_return = [&](s32 ret) -> s32 { + ReleaseFontCtxEntryLock(entry_u8, mode_low, lock_word); + if (ret != ORBIS_OK) { + ClearRenderOutputs(metrics, result); + } + return ret; + }; + + if (((lock_word & 0x40000000u) == 0) || ((lock_word & 0x0FFFFFFFu) == 0)) { + return cleanup_return(rc); + } + + font_obj->reserved_0x04 = static_cast(font->flags >> 15); + font_obj->font_handle = fontHandle; + font_obj->shift_units_x = shift_x_units; + font_obj->shift_units_y = shift_y_units; + font_obj->layout_seed_pair = 0; + + (void)StyleStateGetScalePixel(style_state, &font_obj->scale_x_0x50, &font_obj->scale_y_0x54); + + scale_x_in *= sys_scale_factor; + scale_y_in *= sys_scale_factor; + + float out_scale_x = 0.0f; + float out_scale_y = 0.0f; + if (scale_unit == 0) { + rc = driver->set_char_default_dpi(font_obj, scale_x_in, scale_y_in, &out_scale_x, + &out_scale_y); + } else { + if (dpi_x == 0) { + dpi_x = 0x48; + } + if (dpi_y == 0) { + dpi_y = 0x48; + } + rc = driver->set_char_with_dpi(font_obj, dpi_x, dpi_y, scale_x_in, scale_y_in, &out_scale_x, + &out_scale_y); + } + if (rc != ORBIS_OK) { + return cleanup_return(ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH); + } + + u32 glyph_index = 0; + rc = driver->glyph_index(font_obj, mapped_code, &glyph_index); + if (rc != ORBIS_OK || glyph_index == 0) { + return cleanup_return(ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH); + } + + if (special_case_font_id != 0xffffffffu) { + ApplyGlyphSpecialCaseAdjust(static_cast(special_case_font_id), + static_cast(glyph_index), + reinterpret_cast(&font_obj->layout_seed_pair)); + } + + rc = RenderGlyphIndexToSurface(*font_obj, glyph_index, surf, x, y, metrics, result); + return cleanup_return(rc); +} + +s32 RenderGlyphImageCore(Libraries::Font::OrbisFontGlyph fontGlyph, + Libraries::Font::OrbisFontStyleFrame* fontStyleFrame, + Libraries::Font::OrbisFontRenderer fontRenderer, + Libraries::Font::OrbisFontRenderSurface* surface, float x, float y, + int mode, Libraries::Font::OrbisFontGlyphMetrics* metrics, + Libraries::Font::OrbisFontRenderOutput* result) { + if (!fontRenderer) { + ClearRenderOutputs(metrics, result); + return ORBIS_FONT_ERROR_INVALID_RENDERER; + } + if (!surface || !metrics || !result) { + ClearRenderOutputs(metrics, result); + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + if (!fontGlyph || fontGlyph->magic != 0x0F03) { + ClearRenderOutputs(metrics, result); + return ORBIS_FONT_ERROR_INVALID_GLYPH; + } + + auto* gg = TryGetGeneratedGlyph(fontGlyph); + if (!gg) { + ClearRenderOutputs(metrics, result); + return ORBIS_FONT_ERROR_INVALID_GLYPH; + } + auto* st = TryGetState(gg->owner_handle); + if (!st) { + ClearRenderOutputs(metrics, result); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + const auto previous_renderer = st->bound_renderer; + st->bound_renderer = fontRenderer; + + RenderSurfaceSystemUse* sys = GetSurfaceSystemUse(surface); + const OrbisFontStyleFrame* surface_frame = nullptr; + if ((surface->styleFlag & 0x1) != 0 && sys && ValidateStyleFramePtr(sys->styleframe)) { + surface_frame = sys->styleframe; + } + const OrbisFontStyleFrame* call_frame = + ValidateStyleFramePtr(fontStyleFrame) ? fontStyleFrame : nullptr; + + const StyleFrameScaleState style_scale = + ResolveMergedStyleFrameScale(surface_frame, call_frame, *st); + + float x_used = x; + float y_used = y; + const s16 baseline = static_cast(fontGlyph->baseline); + char baseline_mode = 0; + s16 metrics_axis = 1; + float baseline_x = 0.0f; + float baseline_y = 0.0f; + + if (mode == 2) { + metrics_axis = 2; + baseline_mode = static_cast(-0x56); + if (baseline < 0) { + baseline_mode = 0; + } + } else if (mode == 1) { + metrics_axis = 1; + baseline_mode = static_cast(-0x78); + if (baseline >= 0) { + baseline_mode = 0; + } + } else { + baseline_mode = (baseline < 0) ? '`' : '\x06'; + metrics_axis = static_cast(1 - (baseline >> 15)); + + if ((surface->styleFlag & 0x1) != 0 && sys && surface_frame) { + const float cached = sys->catchedScale; + if (std::abs(cached) > kScaleEpsilon) { + if (baseline >= 1) { + y_used += cached; + baseline_mode = 0; + } else if (baseline < 0) { + x_used += cached; + metrics_axis = 2; + baseline_mode = 0; + } + } + } + } + + if (baseline_mode == '`') { + baseline_x = static_cast(baseline); + } else if (baseline_mode == '\x06') { + baseline_y = static_cast(baseline); + } + + FT_Face face = nullptr; + if (!ResolveFace(*st, gg->owner_handle, face)) { + ClearRenderOutputs(metrics, result); + st->bound_renderer = previous_renderer; + return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH; + } + + float scale_y = gg->scale_y_used; + if (scale_y <= kScaleEpsilon) { + const float pixel_h = + (fontGlyph->base_scale > kScaleEpsilon) ? fontGlyph->base_scale : st->scale_h; + scale_y = ComputeScaleExtForState(*st, face, pixel_h); + } + float scale_x = gg->scale_x_used > kScaleEpsilon ? gg->scale_x_used : scale_y; + + if (style_scale.scale_overridden) { + float style_scale_y = 0.0f; + if (ResolveFaceAndScale(*st, gg->owner_handle, style_scale.scale_h, face, style_scale_y)) { + scale_y = style_scale_y; + scale_x = (style_scale.scale_h > kScaleEpsilon) + ? style_scale_y * (style_scale.scale_w / style_scale.scale_h) + : style_scale_y; + } + } else if (fontGlyph->base_scale > kScaleEpsilon && fontGlyph->scale_x > kScaleEpsilon) { + scale_x = scale_y * (fontGlyph->scale_x / fontGlyph->base_scale); + } + + (void)metrics_axis; + (void)baseline_x; + (void)baseline_y; + + const s32 rc = + RenderCodepointToSurfaceWithScale(*st, gg->owner_handle, face, scale_x, scale_y, surface, + gg->codepoint, x_used, y_used, metrics, result); + + st->bound_renderer = previous_renderer; + return rc; +} + +s32 StyleStateSetScalePixel(void* style_state_block, float w, float h) { + auto* st = static_cast(style_state_block); + if (!st) { + return 0; + } + if (st->scale_w == w) { + if (st->scale_h == h && st->scale_unit == 0) { + return 0; + } + } + st->scale_unit = 0; + st->scale_w = w; + st->scale_h = h; + return 1; +} + +s32 StyleStateSetScalePoint(void* style_state_block, float w, float h) { + auto* st = static_cast(style_state_block); + if (!st) { + return 0; + } + if (st->scale_w == w) { + if (st->scale_h == h && st->scale_unit == 1) { + return 0; + } + } + st->scale_unit = 1; + st->scale_w = w; + st->scale_h = h; + return 1; +} + +s32 StyleStateGetScalePixel(const void* style_state_block, float* w, float* h) { + const auto* st = static_cast(style_state_block); + if (!st || (!w && !h)) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + + const u32 flag = st->scale_unit; + if (w) { + float out = st->scale_w; + const u32 dpi = st->dpi_x; + if (flag != 0 && dpi != 0) { + out *= static_cast(dpi) / kPointsPerInch; + } + *w = out; + if (!h) { + return ORBIS_OK; + } + } + + float out = st->scale_h; + const u32 dpi = st->dpi_y; + if (flag != 0 && dpi != 0) { + out *= static_cast(dpi) / kPointsPerInch; + } + *h = out; + return ORBIS_OK; +} + +s32 StyleStateGetScalePoint(const void* style_state_block, float* w, float* h) { + const auto* st = static_cast(style_state_block); + if (!st || (!w && !h)) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + + const u32 flag = st->scale_unit; + if (w) { + float out = st->scale_w; + const u32 dpi = st->dpi_x; + if (flag == 0 && dpi != 0) { + out *= kPointsPerInch / static_cast(dpi); + } + *w = out; + if (!h) { + return ORBIS_OK; + } + } + + float out = st->scale_h; + const u32 dpi = st->dpi_y; + if (flag == 0 && dpi != 0) { + out *= kPointsPerInch / static_cast(dpi); + } + *h = out; + return ORBIS_OK; +} + +s32 StyleStateSetDpi(u32* dpi_pair, u32 h_dpi, u32 v_dpi) { + if (!dpi_pair) { + return 0; + } + if (h_dpi == 0) { + h_dpi = 0x48; + } + if (v_dpi == 0) { + v_dpi = 0x48; + } + if (dpi_pair[0] == h_dpi && dpi_pair[1] == v_dpi) { + return 0; + } + dpi_pair[0] = h_dpi; + dpi_pair[1] = v_dpi; + return 1; +} + +s32 StyleStateSetSlantRatio(void* style_state_block, float slantRatio) { + auto* st = static_cast(style_state_block); + if (!st) { + return 0; + } + const float prev = st->slant_ratio; + if (prev == slantRatio) { + return 0; + } + + float clamped = slantRatio; + if (clamped > 1.0f) { + clamped = 1.0f; + } else if (clamped < -1.0f) { + clamped = -1.0f; + } + st->slant_ratio = clamped; + return 1; +} + +s32 StyleStateGetSlantRatio(const void* style_state_block, float* slantRatio) { + const auto* st = static_cast(style_state_block); + if (!st || !slantRatio) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + *slantRatio = st->slant_ratio; + return ORBIS_OK; +} + +s32 StyleStateSetWeightScale(void* style_state_block, float weightXScale, float weightYScale) { + auto* st = static_cast(style_state_block); + if (!st) { + return 0; + } + + constexpr float kClamp = 0.04f; + auto clamp_delta = [&](float delta) { + if (delta > kClamp) { + return kClamp; + } + if (delta < -kClamp) { + return -kClamp; + } + return delta; + }; + + bool changed = false; + const float dx = clamp_delta(weightXScale - 1.0f); + const float prev_x = st->effect_weight_x; + if (!(prev_x == dx)) { + st->effect_weight_x = dx; + changed = true; + } + + const float dy = clamp_delta(weightYScale - 1.0f); + const float prev_y = st->effect_weight_y; + if (!(prev_y == dy)) { + st->effect_weight_y = dy; + changed = true; + } + + return changed ? 1 : 0; +} + +s32 StyleStateGetWeightScale(const void* style_state_block, float* weightXScale, + float* weightYScale, u32* mode) { + if ((reinterpret_cast(weightXScale) | + reinterpret_cast(weightYScale) | reinterpret_cast(mode)) == + 0) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + const auto* st = static_cast(style_state_block); + if (!st) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + + if (weightXScale) { + *weightXScale = st->effect_weight_x + 1.0f; + } + if (weightYScale) { + *weightYScale = st->effect_weight_y + 1.0f; + } + if (mode) { + *mode = 0; + } + return ORBIS_OK; +} + +} // namespace Libraries::Font::Internal + +namespace Libraries::FontFt::Internal { + +namespace { + +static std::once_flag g_driver_table_once; +alignas(Libraries::Font::Internal::SysDriver) static Libraries::Font::Internal::SysDriver + g_driver_table{}; + +static std::once_flag g_renderer_table_once; +alignas(Libraries::FontFt::OrbisFontRendererSelection) static Libraries::FontFt:: + OrbisFontRendererSelection g_renderer_table{}; + +using Libraries::Font::Internal::FontLibOpaque; +using Libraries::Font::Internal::FontObj; + +static constexpr float kOneOver64 = 1.0f / 64.0f; + +static std::optional ResolveKnownSysFontAlias( + const std::filesystem::path& sysfonts_dir, std::string_view ps4_filename) { + const auto resolve_existing = + [&](std::string_view filename) -> std::optional { + const std::filesystem::path file_path{std::string(filename)}; + std::error_code ec; + { + const auto candidate = sysfonts_dir / file_path; + if (std::filesystem::exists(candidate, ec)) { + return candidate; + } + } + { + const auto candidate = sysfonts_dir / "font" / file_path; + if (std::filesystem::exists(candidate, ec)) { + return candidate; + } + } + { + const auto candidate = sysfonts_dir / "font2" / file_path; + if (std::filesystem::exists(candidate, ec)) { + return candidate; + } + } + return std::nullopt; + }; + + static constexpr std::array, 41> kAliases = {{ + {"SST-EU-ROMAN-L.OTF", "SST-Light.otf"}, + {"SST-EU-ROMAN.OTF", "SST-Roman.otf"}, + {"SST-EU-ROMAN-M.OTF", "SST-Medium.otf"}, + {"SST-EU-ROMAN-R.OTF", "SST-Roman.otf"}, + {"SST-EU-ROMAN-I.OTF", "SST-Italic.otf"}, + {"SST-EU-ROMAN-B.OTF", "SST-Bold.otf"}, + {"SST-EU-ROMAN-BI.OTF", "SST-BoldItalic.otf"}, + {"SST-ITALIC-L.OTF", "SST-LightItalic.otf"}, + {"SST-ITALIC-R.OTF", "SST-Italic.otf"}, + {"SST-ITALIC-M.OTF", "SST-MediumItalic.otf"}, + {"SST-ITALIC-B.OTF", "SST-BoldItalic.otf"}, + {"SST-TYPEWRITER-R.OTF", "SSTTypewriter-Roman.otf"}, + {"SST-TYPEWRITER-B.OTF", "SSTTypewriter-Bd.otf"}, + {"SST-JPPRO-R.OTF", "SSTJpPro-Regular.otf"}, + {"SST-JPPRO-B.OTF", "SSTJpPro-Bold.otf"}, + {"SST-CNGB-HEI-R.TTF", "DFHEI5-SONY.ttf"}, + {"SST-ARIB-STD-B24-R.TTF", "SSTAribStdB24-Regular.ttf"}, + {"SST-ARABIC-R.OTF", "SSTArabic-Roman.otf"}, + {"SST-ARABIC-L.OTF", "SSTArabic-Light.otf"}, + {"SST-ARABIC-M.OTF", "SSTArabic-Medium.otf"}, + {"SST-ARABIC-B.OTF", "SSTArabic-Bold.otf"}, + {"SCE-EXT-HANGUL-L.OTF", "SCEPS4Yoongd-Light.otf"}, + {"SCE-EXT-HANGUL-R.OTF", "SCEPS4Yoongd-Medium.otf"}, + {"SCE-EXT-HANGUL-B.OTF", "SCEPS4Yoongd-Bold.otf"}, + {"SSTCC-SERIF-MONO.OTF", "e046323ms.ttf"}, + {"SSTCC-SERIF.OTF", "e046323ts.ttf"}, + {"SSTCC-SANSSERIF-MONO.OTF", "n023055ms.ttf"}, + {"SSTCC-SANSSERIF.OTF", "n023055ts.ttf"}, + {"SSTCC-CUSUAL.OTF", "d013013ds.ttf"}, + {"SSTCC-CURSIVE.OTF", "k006004ds.ttf"}, + {"SSTCC-SMALLCAPITAL.OTF", "c041056ts.ttf"}, + {"SCE-JP-CATTLEYA-L.OTF", "SCE-RDC-R-JPN.otf"}, + {"SCE-JP-CATTLEYA-B.OTF", "SCE-RDC-B-JPN.otf"}, + {"SST-THAI-L.OTF", "SSTThai-Light.otf"}, + {"SST-THAI-R.OTF", "SSTThai-Roman.otf"}, + {"SST-THAI-M.OTF", "SSTThai-Medium.otf"}, + {"SST-THAI-B.OTF", "SSTThai-Bold.otf"}, + {"SST-VIETNAMESE-L.OTF", "SSTVietnamese-Light.otf"}, + {"SST-VIETNAMESE-R.OTF", "SSTVietnamese-Roman.otf"}, + {"SST-VIETNAMESE-M.OTF", "SSTVietnamese-Medium.otf"}, + {"SST-VIETNAMESE-B.OTF", "SSTVietnamese-Bold.otf"}, + }}; + + for (const auto& [from, to] : kAliases) { + if (ps4_filename == from) { + return resolve_existing(to); + } + if (ps4_filename == to) { + if (auto reverse = resolve_existing(from)) { + return reverse; + } + } + } + return std::nullopt; +} + +static constexpr u32 MakeTag(char a, char b, char c, char d) { + return (static_cast(static_cast(a)) << 24) | + (static_cast(static_cast(b)) << 16) | + (static_cast(static_cast(c)) << 8) | static_cast(static_cast(d)); +} + +static bool ResolveSfntBaseOffset(const u8* data, std::size_t size, u32 subFontIndex, + u32& out_base) { + out_base = 0; + if (!data || size < 4) { + return false; + } + + const u32 sig = static_cast(static_cast(data))->value(); + if (sig != MakeTag('t', 't', 'c', 'f')) { + return true; + } + + if (size < sizeof(TtcHeader)) { + return false; + } + + const auto* header = static_cast(static_cast(data)); + const u32 num_fonts = header->num_fonts.value(); + if (num_fonts == 0 || subFontIndex >= num_fonts) { + return false; + } + + const std::size_t offsets_off = 0x0C; + const std::size_t want_off = offsets_off + static_cast(subFontIndex) * 4; + if (want_off + 4 > size) { + return false; + } + + const u32 base = static_cast(static_cast(data + want_off))->value(); + if (base > size || (size - base) < 0x0C) { + return false; + } + out_base = base; + return true; +} + +static bool FindSfntTable(const u8* data, std::size_t size, u32 base, u32 tag, u32& out_off, + u32& out_len) { + out_off = 0; + out_len = 0; + if (!data || base > size || (size - base) < 0x0C) { + return false; + } + + const u8* sfnt = data + base; + const std::size_t sfnt_size = size - base; + + if (sfnt_size < sizeof(SfntOffsetTable)) { + return false; + } + const auto* offset_table = static_cast(static_cast(sfnt)); + const u16 num_tables = offset_table->num_tables.value(); + const std::size_t dir_off = sizeof(SfntOffsetTable); + const std::size_t record_size = sizeof(SfntTableRecord); + const std::size_t dir_size = dir_off + static_cast(num_tables) * record_size; + if (dir_size > sfnt_size) { + return false; + } + + for (u16 i = 0; i < num_tables; i++) { + const auto* rec = static_cast( + static_cast(sfnt + dir_off + static_cast(i) * record_size)); + const u32 rec_tag = rec->tag.value(); + if (rec_tag != tag) { + continue; + } + const u32 off = rec->offset.value(); + const u32 len = rec->length.value(); + if (off > sfnt_size || len > (sfnt_size - off)) { + return false; + } + out_off = base + off; + out_len = len; + return true; + } + return false; +} + +static bool ReadUnitsPerEm(const u8* data, std::size_t size, u32 base, u16& out_units) { + out_units = 0; + u32 head_off = 0; + u32 head_len = 0; + if (!FindSfntTable(data, size, base, MakeTag('h', 'e', 'a', 'd'), head_off, head_len)) { + return false; + } + if (head_len < 0x14 || head_off + 0x14 > size) { + return false; + } + out_units = + static_cast(static_cast(data + head_off + 0x12))->value(); + return out_units != 0; +} + +static std::mutex g_font_obj_sidecars_mutex; +static std::unordered_map g_font_obj_sidecars; + +static void SetFontObjSidecar(const FontObj* obj, FontObjSidecar sidecar) { + if (!obj) { + return; + } + std::lock_guard lock(g_font_obj_sidecars_mutex); + g_font_obj_sidecars.insert_or_assign(obj, std::move(sidecar)); +} + +static std::optional TakeFontObjSidecar(const FontObj* obj) { + if (!obj) { + return std::nullopt; + } + std::lock_guard lock(g_font_obj_sidecars_mutex); + auto it = g_font_obj_sidecars.find(obj); + if (it == g_font_obj_sidecars.end()) { + return std::nullopt; + } + FontObjSidecar out = std::move(it->second); + g_font_obj_sidecars.erase(it); + return out; +} + +static void* FtAlloc(FT_Memory memory, long size) { + if (!memory || size <= 0) { + return nullptr; + } + auto* ctx = static_cast(memory->user); + if (!ctx || !ctx->alloc_vtbl) { + return nullptr; + } + const auto alloc_fn = reinterpret_cast(ctx->alloc_vtbl[0]); + return alloc_fn ? alloc_fn(ctx->alloc_ctx, static_cast(size)) : nullptr; +} + +static void FtFree(FT_Memory memory, void* block) { + if (!memory || !block) { + return; + } + auto* ctx = static_cast(memory->user); + if (!ctx || !ctx->alloc_vtbl) { + return; + } + const auto free_fn = reinterpret_cast(ctx->alloc_vtbl[1]); + if (free_fn) { + free_fn(ctx->alloc_ctx, block); + } +} + +static void* FtRealloc(FT_Memory memory, long cur_size, long new_size, void* block) { + if (!memory) { + return nullptr; + } + auto* ctx = static_cast(memory->user); + if (!ctx || !ctx->alloc_vtbl) { + return nullptr; + } + const auto realloc_fn = reinterpret_cast(ctx->alloc_vtbl[2]); + if (realloc_fn) { + return realloc_fn(ctx->alloc_ctx, block, static_cast(new_size)); + } + + if (new_size <= 0) { + FtFree(memory, block); + return nullptr; + } + void* out = FtAlloc(memory, new_size); + if (!out) { + return nullptr; + } + if (block && cur_size > 0) { + std::memcpy(out, block, static_cast(std::min(cur_size, new_size))); + } + FtFree(memory, block); + return out; +} + +} // namespace + +const Libraries::FontFt::OrbisFontLibrarySelection* GetDriverTable() { + std::call_once(g_driver_table_once, [] { + auto* selection = + reinterpret_cast(&g_driver_table); + selection->magic = 0; + selection->reserved = 0; + selection->reserved_ptr1 = nullptr; + + auto* driver = &g_driver_table; + driver->pixel_resolution = &Libraries::FontFt::Internal::LibraryGetPixelResolutionStub; + driver->init = &Libraries::FontFt::Internal::LibraryInitStub; + driver->term = &Libraries::FontFt::Internal::LibraryTermStub; + driver->support_formats = &Libraries::FontFt::Internal::LibrarySupportStub; + driver->open = &Libraries::FontFt::Internal::LibraryOpenFontMemoryStub; + driver->close = &Libraries::FontFt::Internal::LibraryCloseFontObjStub; + driver->scale = &Libraries::FontFt::Internal::LibraryGetFaceScaleStub; + driver->metric = &Libraries::FontFt::Internal::LibraryGetFaceMetricStub; + driver->glyph_index = &Libraries::FontFt::Internal::LibraryGetGlyphIndexStub; + driver->set_char_with_dpi = &Libraries::FontFt::Internal::LibrarySetCharSizeWithDpiStub; + driver->set_char_default_dpi = + &Libraries::FontFt::Internal::LibrarySetCharSizeDefaultDpiStub; + driver->compute_layout = &Libraries::FontFt::Internal::LibraryComputeLayoutBlockStub; + driver->compute_layout_alt = &Libraries::FontFt::Internal::LibraryComputeLayoutAltBlockStub; + driver->load_glyph_cached = &Libraries::FontFt::Internal::LibraryLoadGlyphCachedStub; + driver->get_glyph_metrics = &Libraries::FontFt::Internal::LibraryGetGlyphMetricsStub; + driver->apply_glyph_adjust = &Libraries::FontFt::Internal::LibraryApplyGlyphAdjustStub; + driver->configure_glyph = &Libraries::FontFt::Internal::LibraryConfigureGlyphStub; + }); + + return reinterpret_cast(&g_driver_table); +} + +const Libraries::FontFt::OrbisFontRendererSelection* GetRendererSelectionTable() { + std::call_once(g_renderer_table_once, [] { + g_renderer_table.magic = 0; + g_renderer_table.size = + static_cast(sizeof(Libraries::Font::Internal::RendererFtOpaque)); + g_renderer_table.create_fn = + reinterpret_cast(&Libraries::FontFt::Internal::FtRendererCreate); + g_renderer_table.destroy_fn = + reinterpret_cast(&Libraries::FontFt::Internal::FtRendererDestroy); + g_renderer_table.query_fn = + reinterpret_cast(&Libraries::FontFt::Internal::FtRendererQuery); + }); + return &g_renderer_table; +} + +u32 PS4_SYSV_ABI LibraryGetPixelResolutionStub() { + return 0x40; +} + +s32 PS4_SYSV_ABI LibraryInitStub(const void* memory, void* library) { + if (!memory || !library) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + + const auto* mem = static_cast(memory); + if (!mem->iface || !mem->iface->alloc || !mem->iface->dealloc) { + return ORBIS_FONT_ERROR_INVALID_MEMORY; + } + + const auto alloc_fn = reinterpret_cast(mem->iface->alloc); + const auto free_fn = reinterpret_cast(mem->iface->dealloc); + if (!alloc_fn || !free_fn) { + return ORBIS_FONT_ERROR_INVALID_MEMORY; + } + + void* alloc_ctx = mem->mspace_handle; + void** alloc_vtbl = + reinterpret_cast(const_cast(mem->iface)); + + auto* ctx = static_cast(alloc_fn(alloc_ctx, sizeof(FtLibraryCtx))); + if (!ctx) { + return ORBIS_FONT_ERROR_ALLOCATION_FAILED; + } + std::memset(ctx, 0, sizeof(FtLibraryCtx)); + ctx->alloc_ctx = alloc_ctx; + ctx->alloc_vtbl = alloc_vtbl; + + FT_Memory ft_mem = static_cast(alloc_fn(alloc_ctx, sizeof(FT_MemoryRec_))); + if (!ft_mem) { + free_fn(alloc_ctx, ctx); + return ORBIS_FONT_ERROR_ALLOCATION_FAILED; + } + std::memset(ft_mem, 0, sizeof(*ft_mem)); + ft_mem->user = ctx; + ft_mem->alloc = &FtAlloc; + ft_mem->free = &FtFree; + ft_mem->realloc = &FtRealloc; + ctx->ft_memory = ft_mem; + + FT_Library ft_lib = nullptr; + const FT_Error ft_err = FT_New_Library(ft_mem, &ft_lib); + if (ft_err != 0 || !ft_lib) { + free_fn(alloc_ctx, ft_mem); + free_fn(alloc_ctx, ctx); + return ORBIS_FONT_ERROR_ALLOCATION_FAILED; + } + FT_Add_Default_Modules(ft_lib); + ctx->ft_lib = ft_lib; + + auto* lib = static_cast(library); + lib->fontset_registry = ctx; + lib->flags = 0x60000000; + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI FtRendererCreate(void* renderer) { + if (!renderer) { + return ORBIS_FONT_ERROR_INVALID_RENDERER; + } + + auto* r = static_cast(renderer); + r->ft_backend.renderer_header_0x10 = static_cast(&r->base.mem_kind); + r->ft_backend.unknown_0x08 = 0; + r->ft_backend.unknown_0x10 = 0; + r->ft_backend.unknown_0x18 = 0; + r->ft_backend.initialized_marker = r; + return ORBIS_OK; +} + +u64 PS4_SYSV_ABI FtRendererQuery(void* /*renderer*/, u8* /*params*/, s64* out_ptr, + u8 (*out_vec)[16]) { + if (out_vec) { + std::memset(out_vec, 0, sizeof(*out_vec)); + } + if (out_ptr) { + *out_ptr = 0; + } + return 0; +} + +s32 PS4_SYSV_ABI FtRendererDestroy(void* renderer) { + if (!renderer) { + return ORBIS_FONT_ERROR_INVALID_RENDERER; + } + auto* r = static_cast(renderer); + r->ft_backend.initialized_marker = nullptr; + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI LibraryTermStub(void* library) { + if (!library) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + + auto* lib = static_cast(library); + auto* alloc_ctx = lib->alloc_ctx; + auto* alloc_vtbl = lib->alloc_vtbl; + const auto free_fn = alloc_vtbl ? reinterpret_cast(alloc_vtbl[1]) : nullptr; + if (!free_fn) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + + auto* ctx = static_cast(lib->fontset_registry); + if (!ctx) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + + if (ctx->ft_lib) { + FT_Done_Library(ctx->ft_lib); + ctx->ft_lib = nullptr; + } + if (ctx->ft_memory) { + free_fn(alloc_ctx, ctx->ft_memory); + ctx->ft_memory = nullptr; + } + free_fn(alloc_ctx, ctx); + lib->fontset_registry = nullptr; + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI LibrarySupportStub(void* library, u32 formats) { + if (!library) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + auto* lib = static_cast(library); + if (!lib->fontset_registry) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + + u32 mapped = formats * 2u & 0x100u; + const u32 inv_formats = ~formats; + u32 tmp = mapped + 0x4CC0u; + if ((inv_formats & 0x52u) != 0) { + tmp = mapped; + } + mapped = tmp | 0x4C40u; + if ((inv_formats & 0x42u) != 0) { + mapped = tmp; + } + tmp = mapped | 0x4C80u; + if ((inv_formats & 0x50u) != 0) { + tmp = mapped; + } + mapped = tmp | 0x0C80u; + if ((formats & 0x10u) == 0) { + mapped = tmp; + } + tmp = mapped | 0x8C80u; + if ((inv_formats & 0x30u) != 0) { + tmp = mapped; + } + mapped = tmp | 0x1820u; + if ((formats & 8u) == 0) { + mapped = tmp; + } + tmp = mapped | 0x1808u; + if ((formats & 1u) == 0) { + tmp = mapped; + } + mapped = tmp | 0x1C90u; + if ((inv_formats & 0x14u) != 0) { + mapped = tmp; + } + (void)mapped; + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI LibraryOpenFontMemoryStub(void* library, u32 mode, const void* fontAddress, + u32 fontSize, u32 subFontIndex, u32 /*uniqueWord*/, + void** inoutFontObj) { + if (!library || !fontAddress || !inoutFontObj) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + + auto* lib = static_cast(library); + void* alloc_ctx = lib->alloc_ctx; + void** alloc_vtbl = lib->alloc_vtbl; + const auto alloc_fn = alloc_vtbl ? reinterpret_cast(alloc_vtbl[0]) : nullptr; + const auto free_fn = alloc_vtbl ? reinterpret_cast(alloc_vtbl[1]) : nullptr; + if (!alloc_fn || !free_fn) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + + const u8* data = nullptr; + u32 size = 0; + void* owned_data = nullptr; + std::string open_path; + std::filesystem::path host_path_fs{}; + + if (mode == 1) { + if (fontSize == 0) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + data = static_cast(fontAddress); + size = fontSize; + } else if (mode == 5 || mode == 6 || mode == 7) { + const char* path = static_cast(fontAddress); + if (!path || path[0] == '\0') { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + + open_path = path; + if (path[0] == '/') { + auto* mnt = Common::Singleton::Instance(); + host_path_fs = mnt ? mnt->GetHostPath(path) : std::filesystem::path{}; + if (!host_path_fs.empty()) { + open_path = host_path_fs.string(); + } + } + } else { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + + auto* ctx = static_cast(lib->fontset_registry); + if (!ctx || !ctx->ft_lib) { + if (owned_data) { + free_fn(alloc_ctx, owned_data); + } + return ORBIS_FONT_ERROR_INVALID_LIBRARY; + } + + FT_Face face = nullptr; + FT_Error ft_err = 0; + if (mode == 1) { + ft_err = FT_New_Memory_Face(ctx->ft_lib, reinterpret_cast(data), + static_cast(size), static_cast(subFontIndex), + &face); + } else { + std::vector candidates; + candidates.emplace_back(open_path); + + if (!host_path_fs.empty()) { + const auto sysfonts_dir = host_path_fs.parent_path(); + const auto ps4_name = host_path_fs.filename().string(); + const std::filesystem::path file_path{ps4_name}; + candidates.emplace_back((sysfonts_dir / "font" / file_path).string()); + candidates.emplace_back((sysfonts_dir / "font2" / file_path).string()); + if (const auto alias = ResolveKnownSysFontAlias(sysfonts_dir, ps4_name)) { + candidates.emplace_back(alias->string()); + } + } + + std::error_code ec; + FT_Error last_ft_err = 0; + bool attempted_open = false; + for (const auto& cand : candidates) { + if (cand.empty()) { + continue; + } + if (!std::filesystem::exists(std::filesystem::path{cand}, ec) || ec) { + continue; + } + attempted_open = true; + ft_err = + FT_New_Face(ctx->ft_lib, cand.c_str(), static_cast(subFontIndex), &face); + last_ft_err = ft_err; + if (ft_err == 0 && face) { + break; + } + } + if (ft_err != 0) { + ft_err = last_ft_err; + } else if (!attempted_open) { + ft_err = FT_Err_Cannot_Open_Resource; + } + } + if (ft_err != 0 || !face) { + if (owned_data) { + free_fn(alloc_ctx, owned_data); + } + if (mode == 1) { + return ORBIS_FONT_ERROR_NO_SUPPORT_FORMAT; + } + if (ft_err == FT_Err_Unknown_File_Format) { + return ORBIS_FONT_ERROR_NO_SUPPORT_FORMAT; + } + return ORBIS_FONT_ERROR_FS_OPEN_FAILED; + } + + (void)FT_Select_Charmap(face, FT_ENCODING_UNICODE); + + auto* obj = static_cast(alloc_fn(alloc_ctx, sizeof(FontObj))); + if (!obj) { + FT_Done_Face(face); + if (owned_data) { + free_fn(alloc_ctx, owned_data); + } + return ORBIS_FONT_ERROR_ALLOCATION_FAILED; + } + + std::memset(obj, 0, sizeof(FontObj)); + obj->refcount = 1; + obj->sub_font_index = subFontIndex; + obj->prev = nullptr; + obj->next = static_cast(*inoutFontObj); + if (obj->next) { + obj->next->prev = obj; + } + obj->open_ctx_0x28 = nullptr; + obj->ft_face = face; + obj->font_handle = nullptr; + obj->shift_units_x = 0; + obj->shift_units_y = 0; + obj->layout_seed_pair = 0; + obj->ft_ctx_0x58 = &ctx->ft_lib; + + FontObjSidecar sidecar{}; + sidecar.font_data = data; + sidecar.font_size = size; + sidecar.owned_data = owned_data; + if (mode == 1) { + (void)ResolveSfntBaseOffset(data, size, subFontIndex, sidecar.sfnt_base); + } + SetFontObjSidecar(obj, std::move(sidecar)); + + *inoutFontObj = obj; + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI LibraryCloseFontObjStub(void* fontObj, u32 /*flags*/) { + if (!fontObj) { + return ORBIS_FONT_ERROR_FATAL; + } + + auto* obj = static_cast(fontObj); + if (obj->refcount > 1) { + obj->refcount--; + return ORBIS_OK; + } + + FT_Face face = static_cast(obj->ft_face); + FtLibraryCtx* ctx = nullptr; + if (face && face->memory) { + ctx = static_cast(face->memory->user); + } + + const auto free_fn = + (ctx && ctx->alloc_vtbl) ? reinterpret_cast(ctx->alloc_vtbl[1]) : nullptr; + void* owned_data = nullptr; + if (const auto sidecar = TakeFontObjSidecar(obj)) { + owned_data = sidecar->owned_data; + } + + if (face) { + FT_Done_Face(face); + obj->ft_face = nullptr; + } + if (owned_data && free_fn) { + free_fn(ctx->alloc_ctx, owned_data); + } + if (free_fn) { + FontObj* next = obj->next; + if (obj->prev == nullptr) { + if (next != nullptr) { + next->prev = nullptr; + } + } else { + obj->prev->next = next; + } + free_fn(ctx->alloc_ctx, obj); + return ORBIS_OK; + } + return ORBIS_FONT_ERROR_FATAL; +} + +s32 PS4_SYSV_ABI LibraryGetFaceScaleStub(void* fontObj, u16* outUnitsPerEm, float* outScale) { + if (!fontObj || !outUnitsPerEm || !outScale) { + return ORBIS_FONT_ERROR_FATAL; + } + auto* obj = static_cast(fontObj); + const auto* face = static_cast(obj->ft_face); + if (!face) { + return ORBIS_FONT_ERROR_FATAL; + } + const u16 units = face->units_per_EM; + *outUnitsPerEm = units; + *outScale = static_cast(static_cast(units)) * kOneOver64; + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI LibraryGetFaceMetricStub(void* fontObj, u32 metricId, u16* outMetric) { + if (!fontObj || !outMetric) { + return ORBIS_FONT_ERROR_FATAL; + } + auto* obj = static_cast(fontObj); + const auto* face = static_cast(obj->ft_face); + if (!face) { + return ORBIS_FONT_ERROR_FATAL; + } + + const u16 units = face->units_per_EM; + if (metricId == 0x0e00) { + *outMetric = units; + return ORBIS_OK; + } + if (metricId == 0xea00) { + const TT_OS2* os2 = + static_cast(FT_Get_Sfnt_Table(const_cast(face), ft_sfnt_os2)); + if (os2) { + *outMetric = static_cast(os2->sTypoAscender); + return ORBIS_OK; + } + *outMetric = units; + return ORBIS_FONT_ERROR_NO_SUPPORT_FUNCTION; + } + + *outMetric = 0; + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI LibraryGetGlyphIndexStub(void* fontObj, u32 codepoint_u16, u32* out_glyph_index) { + if (!fontObj || !out_glyph_index) { + return ORBIS_FONT_ERROR_FATAL; + } + auto* obj = static_cast(fontObj); + const FT_Face face = static_cast(obj->ft_face); + if (!face) { + *out_glyph_index = 0; + return ORBIS_FONT_ERROR_FATAL; + } + + const auto glyph_index = + static_cast(FT_Get_Char_Index(face, static_cast(codepoint_u16))); + *out_glyph_index = glyph_index; + return glyph_index ? ORBIS_OK : ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH; +} + +s32 PS4_SYSV_ABI LibrarySetCharSizeWithDpiStub(void* fontObj, u32 dpi_x, u32 dpi_y, float scale_x, + float scale_y, float* out_scale_x, + float* out_scale_y) { + if (!fontObj || !out_scale_x || !out_scale_y) { + return ORBIS_FONT_ERROR_FATAL; + } + auto* obj = static_cast(fontObj); + const FT_Face face = static_cast(obj->ft_face); + if (!face) { + return ORBIS_FONT_ERROR_FATAL; + } + + const auto char_w = static_cast(static_cast(scale_x * 64.0f)); + const auto char_h = static_cast(static_cast(scale_y * 64.0f)); + if (FT_Set_Char_Size(face, char_w, char_h, dpi_x, dpi_y) != 0) { + return ORBIS_FONT_ERROR_FATAL; + } + + const FT_Size size = face->size; + if (!size) { + return ORBIS_FONT_ERROR_FATAL; + } + + const u16 units = face->units_per_EM; + const long x_scale = static_cast(size->metrics.x_scale); + const long y_scale = static_cast(size->metrics.y_scale); + + auto fixed_mul_units_to_f26dot6 = [](long fixed_16_16, u16 units_per_em) -> float { + const long prod = static_cast(static_cast(fixed_16_16) * + static_cast(units_per_em)); + long rounded = prod + 0xFFFF; + if (prod >= 0) { + rounded = prod; + } + const long v = rounded >> 16; + return static_cast(v) * kOneOver64; + }; + + *out_scale_x = fixed_mul_units_to_f26dot6(x_scale, units); + *out_scale_y = fixed_mul_units_to_f26dot6(y_scale, units); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI LibrarySetCharSizeDefaultDpiStub(void* fontObj, float scale_x, float scale_y, + float* out_scale_x, float* out_scale_y) { + if (!fontObj || !out_scale_x || !out_scale_y) { + return ORBIS_FONT_ERROR_FATAL; + } + return LibrarySetCharSizeWithDpiStub(fontObj, 0x48, 0x48, scale_x, scale_y, out_scale_x, + out_scale_y); +} + +s32 PS4_SYSV_ABI LibraryComputeLayoutBlockStub(void* fontObj, const void* style_state_block, + u8 (*out_words)[16]) { + if (!fontObj || !style_state_block || !out_words) { + return ORBIS_FONT_ERROR_FATAL; + } + auto* obj = static_cast(fontObj); + const FT_Face face = static_cast(obj->ft_face); + if (!face || !face->size) { + return ORBIS_FONT_ERROR_FATAL; + } + + const auto* style = + static_cast(style_state_block); + const float effect_width = style->effect_weight_x; + const float effect_height = style->effect_weight_y; + const float slant = style->slant_ratio; + + const s64 y_scale = static_cast(face->size->metrics.y_scale); + const s64 x_scale = static_cast(face->size->metrics.x_scale); + const s64 y_shift = obj->shift_cache_y; + const s64 x_shift = obj->shift_cache_x; + const s64 units_per_em = static_cast(static_cast(face->units_per_EM)); + + auto cvttss2si = [](float v) -> s32 { + if (!std::isfinite(v) || v > 2147483647.0f || v < -2147483648.0f) { + return std::numeric_limits::min(); + } + return static_cast(v); + }; + + auto round_mul_16_16 = [](s64 value, s64 fixed_16_16) -> s32 { + const s64 prod = value * fixed_16_16; + const s64 sign_adj = (value >= 0) ? 0x10000LL : 0LL; + s64 tmp = sign_adj + prod - 0x8000LL; + if (tmp < 0) { + tmp = sign_adj + prod + 0x7FFFLL; + } + return static_cast(static_cast(tmp) >> 16); + }; + + auto trunc_fixed_16_16_to_int = [](s64 fixed_16_16) -> s32 { + s64 tmp = fixed_16_16; + if (fixed_16_16 < 0) { + tmp = fixed_16_16 + 0xFFFFLL; + } + return static_cast(tmp >> 16); + }; + + s32 y_min_px = round_mul_16_16(static_cast(face->bbox.yMin) + y_shift, y_scale); + s32 y_max_px = round_mul_16_16(static_cast(face->bbox.yMax) + y_shift, y_scale); + + s32 half_effect_w_px = 0; + s32 left_adjust_px = 0; + if (effect_width != 0.0f) { + const s32 units_scaled_x = trunc_fixed_16_16_to_int(units_per_em * x_scale); + half_effect_w_px = cvttss2si(effect_width * static_cast(units_scaled_x)) / 2; + left_adjust_px = -half_effect_w_px; + } + + s32 half_effect_h_px = 0; + float out_effect_h = 0.0f; + if (effect_height != 0.0f) { + const s32 units_scaled_y = trunc_fixed_16_16_to_int(units_per_em * y_scale); + half_effect_h_px = cvttss2si(effect_height * static_cast(units_scaled_y)) / 2; + out_effect_h = static_cast(half_effect_h_px) * kOneOver64; + y_min_px -= half_effect_h_px; + y_max_px += half_effect_h_px; + } + + if (slant != 0.0f) { + const s64 shear_16_16 = static_cast(cvttss2si(slant * 65536.0f)); + left_adjust_px += round_mul_16_16(static_cast(y_min_px), shear_16_16); + half_effect_w_px += round_mul_16_16(static_cast(y_max_px), shear_16_16); + } + + auto out = LayoutOutIo{out_words}.fields(); + out.effect_height = out_effect_h; + out.left_adjust = static_cast(left_adjust_px) * kOneOver64; + out.half_effect_width = static_cast(half_effect_w_px) * kOneOver64; + + const s32 x_min_px = round_mul_16_16(static_cast(face->bbox.xMin) + x_shift, x_scale); + const s32 x_max_px = round_mul_16_16(static_cast(face->bbox.xMax) + x_shift, x_scale); + + out.line_advance = static_cast(y_max_px - y_min_px) * kOneOver64; + out.baseline = static_cast(y_max_px) * kOneOver64; + out.x_bound_0 = static_cast(x_min_px + left_adjust_px) * kOneOver64; + out.x_bound_1 = static_cast(x_max_px + half_effect_w_px) * kOneOver64; + + const s64 max_adv_w_units = static_cast(face->max_advance_width) + x_shift; + const s32 max_adv_w_px = round_mul_16_16(max_adv_w_units, x_scale); + out.max_advance_width = static_cast(max_adv_w_px) * kOneOver64; + + float hhea_out = 0.0f; + if (const TT_HoriHeader* hhea = + static_cast(FT_Get_Sfnt_Table(face, ft_sfnt_hhea))) { + const s64 caret_rise_units = x_shift + static_cast(hhea->caret_Slope_Rise); + const s32 caret_rise_px = trunc_fixed_16_16_to_int(caret_rise_units * x_scale); + hhea_out = static_cast(caret_rise_px - half_effect_w_px) * kOneOver64; + } + out.hhea_caret_rise_adjust = hhea_out; + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI LibraryComputeLayoutAltBlockStub(void* fontObj, const void* style_state_block, + u8 (*out_words)[16]) { + if (!fontObj || !style_state_block || !out_words) { + return ORBIS_FONT_ERROR_FATAL; + } + auto* obj = static_cast(fontObj); + const FT_Face face = static_cast(obj->ft_face); + if (!face || !face->size) { + return ORBIS_FONT_ERROR_FATAL; + } + + const auto* style = + static_cast(style_state_block); + const float p18 = style->effect_weight_x; + const float p1c = style->effect_weight_y; + const float p20 = style->slant_ratio; + + const long x_scale = static_cast(face->size->metrics.x_scale); + const long y_scale = static_cast(face->size->metrics.y_scale); + + auto round_fixed_mul = [](long value, long fixed_16_16) -> s32 { + const long long prod = static_cast(value) * static_cast(fixed_16_16); + const long long sign_adj = (value >= 0) ? 0x10000LL : 0LL; + long long tmp = sign_adj + prod - 0x8000LL; + if (tmp < 0) { + tmp = sign_adj + prod + 0x7FFFLL; + } + return static_cast(static_cast(tmp) >> 16); + }; + + const auto* vhea = static_cast(FT_Get_Sfnt_Table(face, ft_sfnt_vhea)); + const long y_shift = static_cast(obj->shift_cache_y); + const long x_shift = static_cast(obj->shift_cache_x); + const long units = static_cast(static_cast(face->units_per_EM)); + + long y_ascender = 0; + long y_descender = 0; + if (vhea) { + y_ascender = static_cast(vhea->Ascender) + y_shift; + y_descender = static_cast(vhea->Descender) + y_shift; + } + + const long left_in = static_cast(face->bbox.xMin) + x_shift; + const long right_in = static_cast(face->bbox.xMax) + x_shift; + + const s32 scaled_left = round_fixed_mul(left_in, x_scale); + const s32 scaled_right = round_fixed_mul(right_in, x_scale); + + s32 x_min_scaled = scaled_left; + s32 x_max_scaled = scaled_right; + s32 x_abs_max = -x_min_scaled; + if (x_abs_max < x_max_scaled) { + x_abs_max = x_max_scaled; + x_min_scaled = -x_max_scaled; + } + + float out_effect_width = 0.0f; + if (p18 != 0.0f) { + const long long prod = static_cast(x_scale) * static_cast(units); + long long rounded = prod + 0xFFFFLL; + if (prod >= 0) { + rounded = prod; + } + const s32 units_scaled = static_cast(rounded >> 16); + const s32 effect = static_cast(std::trunc(p18 * static_cast(units_scaled))) / 2; + out_effect_width = static_cast(effect) * kOneOver64; + } + + if (p1c != 0.0f) { + const long long prod = static_cast(y_scale) * static_cast(units); + long long rounded = prod + 0xFFFFLL; + if (prod >= 0) { + rounded = prod; + } + const s32 units_scaled = static_cast(rounded >> 16); + const s32 delta = static_cast(std::trunc(p1c * static_cast(units_scaled))) / 2; + y_ascender -= delta; + y_descender += delta; + } + + float out_slant_a = 0.0f; + float out_slant_b = 0.0f; + if (p20 != 0.0f) { + const long shear = static_cast(static_cast(std::trunc(p20 * 65536.0f))); + const long long sign_adj_min = (y_ascender < 1) ? 0x10000LL : 0LL; + long long tmp_min = sign_adj_min + + static_cast(-y_ascender) * static_cast(shear) - + 0x8000LL; + if (tmp_min < 0) { + tmp_min = sign_adj_min + 0x7FFFLL + + static_cast(-y_ascender) * static_cast(shear); + } + const long long sign_adj_max = (y_descender >= 0) ? 0x10000LL : 0LL; + long long tmp_max = sign_adj_max + + static_cast(y_descender) * static_cast(shear) - + 0x8000LL; + if (tmp_max < 0) { + tmp_max = sign_adj_max + 0x7FFFLL + + static_cast(y_descender) * static_cast(shear); + } + + const u64 tmp_max_u64 = static_cast(tmp_max); + const u64 tmp_min_u64 = static_cast(tmp_min); + const u64 tmp_max_hi = tmp_max_u64 >> 16; + const bool max_round_bit_clear = ((tmp_max_u64 >> 15) & 1u) == 0; + u64 selected_hi = tmp_max_hi; + s32 selected_lo = static_cast(tmp_min_u64 >> 16); + if (max_round_bit_clear && tmp_max_hi != 0) { + selected_hi = tmp_min_u64 >> 16; + selected_lo = static_cast(tmp_max_u64 >> 16); + } + out_slant_a = static_cast(selected_lo) * kOneOver64; + out_slant_b = static_cast(static_cast(selected_hi)) * kOneOver64; + } + + const s32 lane0 = x_abs_max - x_min_scaled; + const s32 lane1 = -x_abs_max; + const s32 lane2 = round_fixed_mul(y_ascender, x_scale); + const s32 lane3 = round_fixed_mul(y_descender, x_scale); + + auto out = LayoutAltOutIo{out_words}.fields(); + out.metrics_0x00 = static_cast(lane0) * kOneOver64; + out.metrics_0x04 = static_cast(lane1) * kOneOver64; + out.metrics_0x08 = static_cast(lane2) * kOneOver64; + out.metrics_0x0C = static_cast(lane3) * kOneOver64; + + const s32 adv_h_scaled = + round_fixed_mul(static_cast(face->max_advance_height) + y_shift, y_scale); + out.adv_height = static_cast(adv_h_scaled) * kOneOver64; + out.effect_width = out_effect_width; + out.slant_b = out_slant_b; + out.slant_a = out_slant_a; + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI LibraryLoadGlyphCachedStub(void* fontObj, u32 glyphIndex, s32 mode, + std::uint64_t* out_words) { + if (out_words) { + out_words[0] = 0; + out_words[1] = 0; + } + if (!fontObj || !out_words) { + return ORBIS_FONT_ERROR_FATAL; + } + + auto* obj = static_cast(fontObj); + FT_Face face = static_cast(obj->ft_face); + if (!face) { + return ORBIS_FONT_ERROR_FATAL; + } + + FT_GlyphSlot slot = face->glyph; + if (!slot) { + return ORBIS_FONT_ERROR_FATAL; + } + + const u16 units_per_em = static_cast(face->units_per_EM); + const bool cached_match = (obj->cached_glyph_index_0x64 == static_cast(glyphIndex)) && + (obj->cached_units_x_0x68 == static_cast(units_per_em)) && + (obj->cached_units_y_0x70 == obj->cached_units_x_0x68) && (mode == 0); + + auto write_vec88_from_seed_pair_unscaled = [&]() { + const s32 seed_low = + static_cast(static_cast(obj->layout_seed_pair & 0xFFFFFFFFu)); + const s32 seed_high = + static_cast(static_cast((obj->layout_seed_pair >> 32) & 0xFFFFFFFFu)); + obj->layout_seed_vec[0] = static_cast(static_cast(seed_low)); + obj->layout_seed_vec[1] = static_cast(static_cast(seed_high)); + }; + + auto write_out_words_from_slot = [&]() { + const s32 n_contours = static_cast(slot->outline.n_contours); + const s32 n_points = static_cast(slot->outline.n_points); + + const u32 computed_4 = static_cast(n_points * 0x10 + 0x68 + + ((n_points + 0x0F + n_contours * 2) & 0xFFFFFFF0u)); + const u32 computed_8 = static_cast(((n_points + 0x2B + n_contours * 2) & 0xFFFFFFFCu) + + 0x10 + n_points * 4); + + auto* out_u8 = reinterpret_cast(out_words); + *reinterpret_cast(out_u8 + 0) = static_cast(n_contours); + *reinterpret_cast(out_u8 + 2) = static_cast(n_points); + *reinterpret_cast(out_u8 + 4) = computed_4; + *reinterpret_cast(out_u8 + 8) = computed_8; + }; + + if (cached_match) { + obj->shift_cache_x = static_cast(obj->shift_units_x); + obj->shift_cache_y = static_cast(obj->shift_units_y); + write_vec88_from_seed_pair_unscaled(); + write_out_words_from_slot(); + return ORBIS_OK; + } + + const FT_Int32 load_flags = static_cast(((mode == 0) ? 1 : 0) | 0x1A); + const FT_Error ft_err = FT_Load_Glyph(face, static_cast(glyphIndex), load_flags); + if (ft_err != 0) { + obj->cached_glyph_index_0x64 = 0; + obj->cached_units_x_0x68 = 0; + obj->cached_units_y_0x70 = 0; + obj->layout_seed_vec = {}; + obj->layout_scale_vec = {}; + return (ft_err == 0x40) ? ORBIS_FONT_ERROR_ALLOCATION_FAILED + : ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH; + } + + slot = face->glyph; + if (!slot) { + return ORBIS_FONT_ERROR_FATAL; + } + + obj->cached_glyph_index_0x64 = static_cast(glyphIndex); + + if (mode == 0) { + obj->cached_units_x_0x68 = static_cast(units_per_em); + obj->cached_units_y_0x70 = static_cast(units_per_em); + + obj->shift_cache_x = static_cast(obj->shift_units_x); + obj->shift_cache_y = static_cast(obj->shift_units_y); + write_vec88_from_seed_pair_unscaled(); + obj->layout_scale_vec[0] = 0x0000000000010000ull; + obj->layout_scale_vec[1] = 0x0000000000010000ull; + } else { + if (!face->size) { + return ORBIS_FONT_ERROR_FATAL; + } + const long x_scale = static_cast(face->size->metrics.x_scale); + const long y_scale = static_cast(face->size->metrics.y_scale); + + const auto trunc_mul_units = [](long fixed_16_16, u16 units) -> s64 { + const s64 prod = static_cast(fixed_16_16) * static_cast(units); + s64 rounded = prod; + if (prod < 0) { + rounded = prod + 0xFFFFLL; + } + return rounded >> 16; + }; + + const auto round_fixed_mul = [](long fixed_16_16, s32 value) -> s64 { + const long long prod = + static_cast(fixed_16_16) * static_cast(value); + const long long sign_adj = + (static_cast(~static_cast(value)) >> 63) * -0x10000LL; + const long long base = sign_adj + prod; + long long tmp = base - 0x8000LL; + if (tmp < 0) { + tmp = base + 0x7FFFLL; + } + return static_cast(static_cast(static_cast(tmp) >> 16)); + }; + + obj->cached_units_x_0x68 = static_cast(trunc_mul_units(x_scale, units_per_em)); + obj->cached_units_y_0x70 = static_cast(trunc_mul_units(y_scale, units_per_em)); + obj->shift_cache_x = round_fixed_mul(x_scale, obj->shift_units_x); + obj->shift_cache_y = round_fixed_mul(y_scale, obj->shift_units_y); + + const s32 seed_low = + static_cast(static_cast(obj->layout_seed_pair & 0xFFFFFFFFu)); + const s32 seed_high = + static_cast(static_cast((obj->layout_seed_pair >> 32) & 0xFFFFFFFFu)); + const s64 v0 = round_fixed_mul(x_scale, seed_low); + const s64 v1 = round_fixed_mul(y_scale, seed_high); + obj->layout_seed_vec[0] = static_cast(v0); + obj->layout_seed_vec[1] = static_cast(v1); + + u64 lane0 = obj->layout_scale_vec[0]; + u64 lane1 = obj->layout_scale_vec[1]; + std::memcpy(&lane0, &x_scale, sizeof(x_scale)); + std::memcpy(&lane1, &y_scale, sizeof(y_scale)); + obj->layout_scale_vec[0] = lane0; + obj->layout_scale_vec[1] = lane1; + } + + write_out_words_from_slot(); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI LibraryGetGlyphMetricsStub(void* fontObj, std::uint32_t* /*opt_param2*/, + std::uint8_t /*mode*/, std::uint8_t* out_params, + Libraries::Font::OrbisFontGlyphMetrics* out_metrics) { + if (!fontObj || !out_params || !out_metrics) { + return ORBIS_FONT_ERROR_FATAL; + } + + auto* obj = static_cast(fontObj); + FT_Face face = static_cast(obj->ft_face); + if (!face || !face->glyph) { + return ORBIS_FONT_ERROR_FATAL; + } + + const FT_GlyphSlot slot = face->glyph; + out_metrics->width = static_cast(slot->metrics.width) * kOneOver64; + out_metrics->height = static_cast(slot->metrics.height) * kOneOver64; + out_metrics->Horizontal.bearingX = static_cast(slot->metrics.horiBearingX) * kOneOver64; + out_metrics->Horizontal.bearingY = static_cast(slot->metrics.horiBearingY) * kOneOver64; + out_metrics->Horizontal.advance = static_cast(slot->metrics.horiAdvance) * kOneOver64; + out_metrics->Vertical.bearingX = static_cast(slot->metrics.vertBearingX) * kOneOver64; + out_metrics->Vertical.bearingY = static_cast(slot->metrics.vertBearingY) * kOneOver64; + out_metrics->Vertical.advance = static_cast(slot->metrics.vertAdvance) * kOneOver64; + + out_params[0] = 0xF2; + out_params[1] = 0; + + const u8 face_flag_bit = static_cast((static_cast(face->face_flags) >> 5) & 1u); + const u64 vec88_a = obj->layout_seed_vec[0]; + const u64 vec88_b = obj->layout_seed_vec[1]; + const u8 has_vec88 = (vec88_a != 0 || vec88_b != 0) ? 8 : 0; + out_params[2] = static_cast(0xF0 | has_vec88 | face_flag_bit); + out_params[3] = 0; + + const u16 units_per_em = static_cast(face->units_per_EM); + std::memcpy(out_params + 4, &units_per_em, sizeof(units_per_em)); + + void* outline_ptr = static_cast(&slot->outline); + std::memcpy(out_params + 0x10, &outline_ptr, sizeof(outline_ptr)); + + void* metrics_ptr = static_cast(out_metrics); + std::memcpy(out_params + 0x18, &metrics_ptr, sizeof(metrics_ptr)); + + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI LibraryApplyGlyphAdjustStub(void* fontObj, u32 /*p2*/, u32 glyphIndex, s32 p4, + s32 p5, u32* inoutGlyphIndex) { + if (!fontObj || !inoutGlyphIndex) { + return ORBIS_FONT_ERROR_FATAL; + } + auto* obj = static_cast(fontObj); + *inoutGlyphIndex = glyphIndex; + obj->shift_units_x = p4; + obj->shift_units_y = p5; + obj->layout_seed_pair = 0; + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI LibraryConfigureGlyphStub(void* fontObj, std::uint32_t* in_params, s32 mode, + std::uint32_t* inout_state) { + if (!fontObj || !in_params || !inout_state) { + return ORBIS_FONT_ERROR_FATAL; + } + + auto* obj = static_cast(fontObj); + + if (*in_params != 0) { + if (mode == 0) { + reinterpret_cast(inout_state)[3] |= 0x80; + } else { + reinterpret_cast(inout_state)[3] |= 0x80; + } + } + + obj->glyph_cfg_word_0x130 = *in_params; + obj->glyph_cfg_mode_0x134 = static_cast(mode); + obj->glyph_cfg_byte_0x136 = 0; + obj->glyph_cfg_byte_0x135 = 0; + return ORBIS_OK; +} + +} // namespace Libraries::FontFt::Internal diff --git a/src/core/libraries/font/fontft_internal.h b/src/core/libraries/font/fontft_internal.h new file mode 100644 index 000000000..74adf6a25 --- /dev/null +++ b/src/core/libraries/font/fontft_internal.h @@ -0,0 +1,778 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include FT_FREETYPE_H +#include FT_SYSTEM_H + +#include "core/libraries/font/font.h" +#include "core/libraries/font/fontft.h" + +namespace Libraries::Font { + +struct FontHandleOpenInfo { + /*0x00*/ u32 unique_id_packed; + /*0x04*/ u32 ctx_entry_index; + /*0x08*/ u32 sub_font_index; + /*0x0C*/ u32 fontset_flags; + /*0x10*/ const void* fontset_record; + /*0x18*/ u64 reserved_0x18; +}; +static_assert(sizeof(FontHandleOpenInfo) == 0x20, "FontHandleOpenInfo size"); +static_assert(offsetof(FontHandleOpenInfo, sub_font_index) == 0x08, + "FontHandleOpenInfo sub_font_index offset"); +static_assert(offsetof(FontHandleOpenInfo, fontset_record) == 0x10, + "FontHandleOpenInfo fontset_record offset"); + +struct FontHandleStyleStateTail { + /*0x00*/ u32 scale_unit; + /*0x04*/ u32 reserved_0x0c; + /*0x08*/ float scale_w; + /*0x0C*/ float scale_h; + /*0x10*/ float effect_weight_x; + /*0x14*/ float effect_weight_y; + /*0x18*/ float slant_ratio; + /*0x1C*/ float reserved_0x24; +}; +static_assert(sizeof(FontHandleStyleStateTail) == 0x20, "FontHandleStyleStateTail size"); + +struct FontHandleOpaqueNative { + u16 magic; + u16 flags; + u32 lock_word; + FontHandleOpenInfo open_info; + OrbisFontLib library; + struct RendererBinding { + void* renderer; + u8 reserved_0x08[0x10 - sizeof(void*)]; + }; + static_assert(sizeof(RendererBinding) == 0x10, "RendererBinding size"); + union { + u8 reserved_0x30[0x10]; + RendererBinding renderer_binding; + }; + u32 style_frame[2]; + union { + u8 reserved_0x48[0x20]; + FontHandleStyleStateTail style_tail; + }; + OrbisFontStyleFrame cached_style; + u16 metricA; + u16 metricB; + u8 reserved_0xcc[0x1C]; + u8 reserved_0xe8[0x08]; + OrbisFontHandle prevFont; + OrbisFontHandle nextFont; +}; +static_assert(sizeof(FontHandleOpaqueNative) == 0x100, "FontHandleOpaqueNative size"); +static_assert(offsetof(FontHandleOpaqueNative, renderer_binding) == 0x30, + "FontHandleOpaqueNative renderer_binding offset"); +static_assert(offsetof(FontHandleOpaqueNative, magic) == 0x00, + "FontHandleOpaqueNative magic offset"); +static_assert(offsetof(FontHandleOpaqueNative, lock_word) == 0x04, + "FontHandleOpaqueNative lock_word offset"); +static_assert(offsetof(FontHandleOpaqueNative, open_info) == 0x08, + "FontHandleOpaqueNative open_info offset"); +static_assert(offsetof(FontHandleOpaqueNative, library) == 0x28, + "FontHandleOpaqueNative library offset"); +static_assert(offsetof(FontHandleOpaqueNative, reserved_0x30) == 0x30, + "FontHandleOpaqueNative reserved_0x30 offset"); +static_assert(offsetof(FontHandleOpaqueNative, style_frame) == 0x40, + "FontHandleOpaqueNative style_frame offset"); +static_assert(offsetof(FontHandleOpaqueNative, reserved_0x48) == 0x48, + "FontHandleOpaqueNative reserved_0x48 offset"); +static_assert(offsetof(FontHandleOpaqueNative, cached_style) == 0x68, + "FontHandleOpaqueNative cached_style offset"); +static_assert(offsetof(FontHandleOpaqueNative, metricA) == 0xC8, + "FontHandleOpaqueNative metricA offset"); +static_assert(offsetof(FontHandleOpaqueNative, metricB) == 0xCA, + "FontHandleOpaqueNative metricB offset"); +static_assert(offsetof(FontHandleOpaqueNative, prevFont) == 0xF0, + "FontHandleOpaqueNative prev offset"); +static_assert(offsetof(FontHandleOpaqueNative, nextFont) == 0xF8, + "FontHandleOpaqueNative next offset"); + +} // namespace Libraries::Font + +namespace Libraries::Font::Internal { + +struct alignas(16) LayoutWord { + std::array words{}; + + constexpr u8* data() { + return reinterpret_cast(words.data()); + } + constexpr const u8* data() const { + return reinterpret_cast(words.data()); + } +}; +static_assert(sizeof(LayoutWord) == 16, "LayoutWord size"); +static_assert(alignof(LayoutWord) == 16, "LayoutWord alignment"); + +struct alignas(16) HorizontalLayoutMetricsWord { + float line_advance = 0.0f; + float baseline = 0.0f; + float x_bound_lo = 0.0f; + float x_bound_hi = 0.0f; +}; +static_assert(sizeof(HorizontalLayoutMetricsWord) == 16, "HorizontalLayoutMetricsWord size"); +static_assert(alignof(HorizontalLayoutMetricsWord) == 16, "HorizontalLayoutMetricsWord alignment"); + +struct alignas(16) HorizontalLayoutEffectsWord { + u32 unknown_0x00 = 0; + u32 unknown_0x04 = 0; + float effect_height = 0.0f; + float packed_i8_adj_u13_lane1 = 0.0f; +}; +static_assert(sizeof(HorizontalLayoutEffectsWord) == 16, "HorizontalLayoutEffectsWord size"); +static_assert(alignof(HorizontalLayoutEffectsWord) == 16, "HorizontalLayoutEffectsWord alignment"); + +struct alignas(16) HorizontalLayoutAuxWord { + float packed_i8_adj_u13_lane0 = 0.0f; + u32 unknown_0x04 = 0; + u32 unknown_0x08 = 0; + u32 unknown_0x0C = 0; +}; +static_assert(sizeof(HorizontalLayoutAuxWord) == 16, "HorizontalLayoutAuxWord size"); +static_assert(alignof(HorizontalLayoutAuxWord) == 16, "HorizontalLayoutAuxWord alignment"); + +struct alignas(16) HorizontalLayoutBlocks { + HorizontalLayoutMetricsWord metrics; + HorizontalLayoutEffectsWord effects; + HorizontalLayoutAuxWord aux; + + constexpr void* words() { + return &metrics; + } + constexpr const void* words() const { + return &metrics; + } + + struct Values { + float line_advance = 0.0f; + float baseline = 0.0f; + float effect_height = 0.0f; + u64 x_bounds_bits = 0; + u64 i8_adj_u13_bits = 0; + }; + + float line_advance() const { + return metrics.line_advance; + } + + float baseline() const { + return metrics.baseline; + } + + float effect_height() const { + return effects.effect_height; + } + + u64 x_bounds_bits() const { + return static_cast(std::bit_cast(metrics.x_bound_lo)) | + (static_cast(std::bit_cast(metrics.x_bound_hi)) << 32); + } + + u64 i8_adj_u13_bits() const { + return static_cast(std::bit_cast(effects.packed_i8_adj_u13_lane1)) | + (static_cast(std::bit_cast(aux.packed_i8_adj_u13_lane0)) << 32); + } + + Values values() const { + return { + .line_advance = line_advance(), + .baseline = baseline(), + .effect_height = effect_height(), + .x_bounds_bits = x_bounds_bits(), + .i8_adj_u13_bits = i8_adj_u13_bits(), + }; + } + + void set_line_advance(float v) { + metrics.line_advance = v; + } + + void set_baseline(float v) { + metrics.baseline = v; + } + + void set_x_bounds(float lo, float hi) { + metrics.x_bound_lo = lo; + metrics.x_bound_hi = hi; + } + + void set_effect_height(float v) { + effects.effect_height = v; + } + + void set_i8_adj_u13_lanes(float lane0, float lane1) { + aux.packed_i8_adj_u13_lane0 = lane0; + effects.packed_i8_adj_u13_lane1 = lane1; + } +}; +static_assert(sizeof(HorizontalLayoutBlocks) == 0x30, "HorizontalLayoutBlocks size"); +static_assert(alignof(HorizontalLayoutBlocks) == 16, "HorizontalLayoutBlocks alignment"); +static_assert(offsetof(HorizontalLayoutBlocks, metrics) == 0x00, + "HorizontalLayoutBlocks metrics offset"); +static_assert(offsetof(HorizontalLayoutBlocks, effects) == 0x10, + "HorizontalLayoutBlocks effects offset"); +static_assert(offsetof(HorizontalLayoutBlocks, aux) == 0x20, "HorizontalLayoutBlocks aux offset"); + +struct alignas(16) VerticalLayoutMetricsWord { + float column_advance = 0.0f; + float baseline_offset_x = 0.0f; + float unknown_0x08 = 0.0f; + float unknown_0x0C = 0.0f; +}; +static_assert(sizeof(VerticalLayoutMetricsWord) == 16, "VerticalLayoutMetricsWord size"); +static_assert(alignof(VerticalLayoutMetricsWord) == 16, "VerticalLayoutMetricsWord alignment"); + +struct alignas(16) VerticalLayoutDecorationWord { + u32 unknown_0x00 = 0; + float decoration_span = 0.0f; + float unknown_0x08 = 0.0f; + float unknown_0x0C = 0.0f; +}; +static_assert(sizeof(VerticalLayoutDecorationWord) == 16, "VerticalLayoutDecorationWord size"); +static_assert(alignof(VerticalLayoutDecorationWord) == 16, + "VerticalLayoutDecorationWord alignment"); + +struct alignas(16) VerticalLayoutBlocks { + VerticalLayoutMetricsWord metrics; + VerticalLayoutDecorationWord decoration; + + constexpr void* words() { + return &metrics; + } + constexpr const void* words() const { + return &metrics; + } + + float column_advance() const { + return metrics.column_advance; + } + + float baseline_offset_x() const { + return metrics.baseline_offset_x; + } + + float decoration_span() const { + return decoration.decoration_span; + } + + struct Values { + float column_advance = 0.0f; + float baseline_offset_x = 0.0f; + float decoration_span = 0.0f; + }; + + Values values() const { + return { + .column_advance = column_advance(), + .baseline_offset_x = baseline_offset_x(), + .decoration_span = decoration_span(), + }; + } + + void set_column_advance(float v) { + metrics.column_advance = v; + } + + void set_baseline_offset_x(float v) { + metrics.baseline_offset_x = v; + } + + void set_unknown_metrics_0x08(float v) { + metrics.unknown_0x08 = v; + } + + void set_unknown_metrics_0x0C(float v) { + metrics.unknown_0x0C = v; + } + + void set_decoration_span(float v) { + decoration.decoration_span = v; + } + + void set_unknown_decoration_0x08(float v) { + decoration.unknown_0x08 = v; + } + + void set_unknown_decoration_0x0C(float v) { + decoration.unknown_0x0C = v; + } +}; +static_assert(sizeof(VerticalLayoutBlocks) == 0x20, "VerticalLayoutBlocks size"); +static_assert(alignof(VerticalLayoutBlocks) == 16, "VerticalLayoutBlocks alignment"); +static_assert(offsetof(VerticalLayoutBlocks, metrics) == 0x00, + "VerticalLayoutBlocks metrics offset"); +static_assert(offsetof(VerticalLayoutBlocks, decoration) == 0x10, + "VerticalLayoutBlocks decoration offset"); + +struct HorizontalLayoutBlocksIo { + u8 (*words)[16] = nullptr; + + struct F32Field { + u8* base = nullptr; + std::size_t offset = 0; + + float value() const { + float v = 0.0f; + std::memcpy(&v, base + offset, sizeof(v)); + return v; + } + + F32Field& operator=(float v) { + std::memcpy(base + offset, &v, sizeof(v)); + return *this; + } + + operator float() const { + return value(); + } + }; + + struct Fields { + F32Field line_advance; + F32Field baseline; + F32Field x_bound_0; + F32Field x_bound_1; + F32Field effect_height; + F32Field u13_i8_lane0; + F32Field u13_i8_lane1; + }; + + Fields fields() const { + return { + .line_advance = {words ? words[0] : nullptr, 0x00}, + .baseline = {words ? words[0] : nullptr, 0x04}, + .x_bound_0 = {words ? words[0] : nullptr, 0x08}, + .x_bound_1 = {words ? words[0] : nullptr, 0x0C}, + .effect_height = {words ? words[1] : nullptr, 0x08}, + .u13_i8_lane0 = {words ? words[2] : nullptr, 0x00}, + .u13_i8_lane1 = {words ? words[1] : nullptr, 0x0C}, + }; + } +}; + +struct VerticalLayoutBlocksIo { + u8 (*words)[16] = nullptr; + + struct F32Field { + u8* base = nullptr; + std::size_t offset = 0; + + float value() const { + float v = 0.0f; + std::memcpy(&v, base + offset, sizeof(v)); + return v; + } + + F32Field& operator=(float v) { + std::memcpy(base + offset, &v, sizeof(v)); + return *this; + } + + operator float() const { + return value(); + } + }; + + struct Fields { + F32Field column_advance; + F32Field baseline_offset_x; + F32Field decoration_span; + F32Field unknown_metrics_0x08; + F32Field unknown_metrics_0x0C; + F32Field unknown_decoration_0x08; + F32Field unknown_decoration_0x0C; + }; + + Fields fields() const { + return { + .column_advance = {words ? words[0] : nullptr, 0x00}, + .baseline_offset_x = {words ? words[0] : nullptr, 0x04}, + .decoration_span = {words ? words[1] : nullptr, 0x04}, + .unknown_metrics_0x08 = {words ? words[0] : nullptr, 0x08}, + .unknown_metrics_0x0C = {words ? words[0] : nullptr, 0x0C}, + .unknown_decoration_0x08 = {words ? words[1] : nullptr, 0x08}, + .unknown_decoration_0x0C = {words ? words[1] : nullptr, 0x0C}, + }; + } +}; + +struct alignas(16) VerticalLayoutAltBlocks { + struct MetricsWord { + float unknown_0x00 = 0.0f; + float baseline_offset_x_candidate = 0.0f; + float unknown_0x08 = 0.0f; + float unknown_0x0C = 0.0f; + }; + + struct ExtrasWord { + float unknown_0x00 = 0.0f; + float decoration_span_candidate = 0.0f; + float unknown_0x08 = 0.0f; + float unknown_0x0C = 0.0f; + }; + + MetricsWord metrics; + ExtrasWord extras; + + constexpr void* words() { + return &metrics; + } + constexpr const void* words() const { + return &metrics; + } + + struct Values { + MetricsWord metrics{}; + ExtrasWord extras{}; + }; + + Values values() const { + return { + .metrics = metrics, + .extras = extras, + }; + } +}; +static_assert(sizeof(VerticalLayoutAltBlocks) == 0x20, "VerticalLayoutAltBlocks size"); +static_assert(alignof(VerticalLayoutAltBlocks) == 16, "VerticalLayoutAltBlocks alignment"); +static_assert(offsetof(VerticalLayoutAltBlocks, metrics) == 0x00, + "VerticalLayoutAltBlocks metrics offset"); +static_assert(offsetof(VerticalLayoutAltBlocks, extras) == 0x10, + "VerticalLayoutAltBlocks extras offset"); + +struct GetCharGlyphMetricsFailLogState { + u32 count = 0; + bool suppression_logged = false; +}; + +struct SysFontRangeRecord { + /*0x00*/ u32 start; + /*0x04*/ u32 end; + /*0x08*/ s16 shift_x; + /*0x0A*/ s16 shift_y; + /*0x0C*/ float scale_mul; + /*0x10*/ u32 reserved_0x10; + /*0x14*/ u32 reserved_0x14; + /*0x18*/ u32 reserved_0x18; +}; +static_assert(sizeof(SysFontRangeRecord) == 0x1C, "SysFontRangeRecord size"); +static_assert(offsetof(SysFontRangeRecord, shift_x) == 0x08, "SysFontRangeRecord shift_x offset"); +static_assert(offsetof(SysFontRangeRecord, scale_mul) == 0x0C, + "SysFontRangeRecord scale_mul offset"); + +struct F32x2 { + float lo = 0.0f; + float hi = 0.0f; +}; + +struct RenderSurfaceSystemUse { + Libraries::Font::OrbisFontStyleFrame* styleframe = nullptr; + float catchedScale = 0.0f; + std::uint8_t padding[88 - sizeof(Libraries::Font::OrbisFontStyleFrame*) - sizeof(float)]{}; +}; +static_assert(sizeof(RenderSurfaceSystemUse) == + sizeof(((Libraries::Font::OrbisFontRenderSurface*)nullptr)->reserved_q), + "RenderSurfaceSystemUse layout must match OrbisFontRenderSurface::reserved_q"); + +s32 ComputeHorizontalLayoutBlocks(Libraries::Font::OrbisFontHandle fontHandle, + const void* style_state_block, u8 (*out_words)[16]); +s32 ComputeVerticalLayoutBlocks(Libraries::Font::OrbisFontHandle fontHandle, + const void* style_state_block, u8 (*out_words)[16]); + +inline u8 (*LayoutWordsBytes(void* bytes))[16] { + return reinterpret_cast(bytes); +} +inline const u8 (*LayoutWordsBytes(const void* bytes))[16] { + return reinterpret_cast(bytes); +} + +inline s32 ComputeHorizontalLayoutBlocksWords(Libraries::Font::OrbisFontHandle fontHandle, + const void* style_state_block, + LayoutWord* out_words) { + return ComputeHorizontalLayoutBlocks(fontHandle, style_state_block, + reinterpret_cast(out_words)); +} + +inline s32 ComputeHorizontalLayoutBlocksTyped(Libraries::Font::OrbisFontHandle fontHandle, + const void* style_state_block, + HorizontalLayoutBlocks* out_blocks) { + return ComputeHorizontalLayoutBlocks(fontHandle, style_state_block, + reinterpret_cast(out_blocks->words())); +} + +inline s32 ComputeVerticalLayoutBlocksWords(Libraries::Font::OrbisFontHandle fontHandle, + const void* style_state_block, LayoutWord* out_words) { + return ComputeVerticalLayoutBlocks(fontHandle, style_state_block, + reinterpret_cast(out_words)); +} + +inline s32 ComputeVerticalLayoutBlocksTyped(Libraries::Font::OrbisFontHandle fontHandle, + const void* style_state_block, + VerticalLayoutBlocks* out_blocks) { + return ComputeVerticalLayoutBlocks(fontHandle, style_state_block, + reinterpret_cast(out_blocks->words())); +} + +inline s32 ComputeHorizontalLayoutBlocksToBytes(Libraries::Font::OrbisFontHandle fontHandle, + const void* style_state_block, void* out_bytes) { + return ComputeHorizontalLayoutBlocks(fontHandle, style_state_block, + LayoutWordsBytes(out_bytes)); +} + +inline s32 ComputeVerticalLayoutBlocksToBytes(Libraries::Font::OrbisFontHandle fontHandle, + const void* style_state_block, void* out_bytes) { + return ComputeVerticalLayoutBlocks(fontHandle, style_state_block, LayoutWordsBytes(out_bytes)); +} + +struct SysFontDesc { + float scale_factor; + s32 shift_value; +}; + +SysFontDesc GetSysFontDesc(u32 font_id); + +Libraries::Font::FontHandleOpaqueNative* GetNativeFont(Libraries::Font::OrbisFontHandle h); +bool AcquireFontLock(Libraries::Font::FontHandleOpaqueNative* font, u32& out_prev_lock_word); +void ReleaseFontLock(Libraries::Font::FontHandleOpaqueNative* font, u32 prev_lock_word); +bool AcquireCachedStyleLock(Libraries::Font::FontHandleOpaqueNative* font, u32& out_prev_lock_word); +void ReleaseCachedStyleLock(Libraries::Font::FontHandleOpaqueNative* font, u32 prev_lock_word); + +std::uint8_t* AcquireFontCtxEntry(std::uint8_t* ctx, u32 idx, u32 mode_low, void** out_obj, + u32* out_lock_word); + +void ReleaseFontObjectsForHandle(Libraries::Font::OrbisFontHandle fontHandle, int entryCount); + +s32 GetCharGlyphMetrics(Libraries::Font::OrbisFontHandle fontHandle, u32 code, + Libraries::Font::OrbisFontGlyphMetrics* metrics, bool use_cached_style); + +const std::uint8_t* FindSysFontRangeRecord(u32 font_id, u32 codepoint); + +u32 ResolveSysFontCodepoint(const void* record, u64 code, u32 flags, u32* out_font_id); + +s32 RenderCharGlyphImageCore(Libraries::Font::OrbisFontHandle fontHandle, u32 code, + Libraries::Font::OrbisFontRenderSurface* surf, float x, float y, + Libraries::Font::OrbisFontGlyphMetrics* metrics, + Libraries::Font::OrbisFontRenderOutput* result); + +s32 RenderGlyphImageCore(Libraries::Font::OrbisFontGlyph fontGlyph, + Libraries::Font::OrbisFontStyleFrame* fontStyleFrame, + Libraries::Font::OrbisFontRenderer fontRenderer, + Libraries::Font::OrbisFontRenderSurface* surface, float x, float y, + int mode, Libraries::Font::OrbisFontGlyphMetrics* metrics, + Libraries::Font::OrbisFontRenderOutput* result); + +s32 StyleStateSetScalePixel(void* style_state_block, float w, float h); + +s32 StyleStateSetScalePoint(void* style_state_block, float w, float h); + +s32 StyleStateGetScalePixel(const void* style_state_block, float* w, float* h); + +s32 StyleStateGetScalePoint(const void* style_state_block, float* w, float* h); + +s32 StyleStateSetDpi(u32* dpi_pair, u32 h_dpi, u32 v_dpi); + +s32 StyleStateSetSlantRatio(void* style_state_block, float slantRatio); + +s32 StyleStateGetSlantRatio(const void* style_state_block, float* slantRatio); + +s32 StyleStateSetWeightScale(void* style_state_block, float weightXScale, float weightYScale); + +s32 StyleStateGetWeightScale(const void* style_state_block, float* weightXScale, + float* weightYScale, u32* mode); + +} // namespace Libraries::Font::Internal + +namespace Libraries::FontFt::Internal { + +using GuestAllocFn = void*(PS4_SYSV_ABI*)(void* object, u32 size); +using GuestFreeFn = void(PS4_SYSV_ABI*)(void* object, void* p); +using GuestReallocFn = void*(PS4_SYSV_ABI*)(void* object, void* p, u32 size); + +struct FtLibraryCtx { + void* alloc_ctx = nullptr; + void** alloc_vtbl = nullptr; + FT_Memory ft_memory = nullptr; + FT_Library ft_lib = nullptr; +}; + +struct BeU16 { + u8 b[2]; + + constexpr u16 value() const { + return static_cast((static_cast(b[0]) << 8) | static_cast(b[1])); + } +}; +static_assert(sizeof(BeU16) == 2, "BeU16 size"); + +struct BeU32 { + u8 b[4]; + + constexpr u32 value() const { + return (static_cast(b[0]) << 24) | (static_cast(b[1]) << 16) | + (static_cast(b[2]) << 8) | static_cast(b[3]); + } +}; +static_assert(sizeof(BeU32) == 4, "BeU32 size"); + +struct TtcHeader { + BeU32 tag; + BeU32 version; + BeU32 num_fonts; +}; +static_assert(sizeof(TtcHeader) == 0x0C, "TtcHeader size"); + +struct SfntOffsetTable { + BeU32 version; + BeU16 num_tables; + BeU16 search_range; + BeU16 entry_selector; + BeU16 range_shift; +}; +static_assert(sizeof(SfntOffsetTable) == 0x0C, "SfntOffsetTable size"); + +struct SfntTableRecord { + BeU32 tag; + BeU32 checksum; + BeU32 offset; + BeU32 length; +}; +static_assert(sizeof(SfntTableRecord) == 0x10, "SfntTableRecord size"); + +struct FontObjSidecar { + const u8* font_data = nullptr; + u32 font_size = 0; + u32 sfnt_base = 0; + void* owned_data = nullptr; +}; + +struct LayoutOutIo { + u8 (*words)[16] = nullptr; + + struct F32Field { + u8* base = nullptr; + std::size_t offset = 0; + + F32Field& operator=(float v) { + std::memcpy(base + offset, &v, sizeof(v)); + return *this; + } + }; + + struct Fields { + F32Field line_advance; + F32Field baseline; + F32Field x_bound_0; + F32Field x_bound_1; + F32Field max_advance_width; + F32Field hhea_caret_rise_adjust; + F32Field effect_height; + F32Field half_effect_width; + F32Field left_adjust; + }; + + Fields fields() const { + return { + .line_advance = {words[0], 0x00}, + .baseline = {words[0], 0x04}, + .x_bound_0 = {words[0], 0x08}, + .x_bound_1 = {words[0], 0x0C}, + .max_advance_width = {words[1], 0x00}, + .hhea_caret_rise_adjust = {words[1], 0x04}, + .effect_height = {words[1], 0x08}, + .half_effect_width = {words[1], 0x0C}, + .left_adjust = {words[2], 0x00}, + }; + } +}; + +struct LayoutAltOutIo { + u8 (*words)[16] = nullptr; + + struct F32Field { + u8* base = nullptr; + std::size_t offset = 0; + + F32Field& operator=(float v) { + std::memcpy(base + offset, &v, sizeof(v)); + return *this; + } + }; + + struct Fields { + F32Field metrics_0x00; + F32Field metrics_0x04; + F32Field metrics_0x08; + F32Field metrics_0x0C; + F32Field adv_height; + F32Field effect_width; + F32Field slant_b; + F32Field slant_a; + }; + + Fields fields() const { + return { + .metrics_0x00 = {words[0], 0x00}, + .metrics_0x04 = {words[0], 0x04}, + .metrics_0x08 = {words[0], 0x08}, + .metrics_0x0C = {words[0], 0x0C}, + .adv_height = {words[1], 0x00}, + .effect_width = {words[1], 0x04}, + .slant_b = {words[1], 0x08}, + .slant_a = {words[1], 0x0C}, + }; + } +}; + +const Libraries::FontFt::OrbisFontLibrarySelection* GetDriverTable(); +const Libraries::FontFt::OrbisFontRendererSelection* GetRendererSelectionTable(); + +u32 PS4_SYSV_ABI LibraryGetPixelResolutionStub(); +s32 PS4_SYSV_ABI LibraryInitStub(const void* memory, void* library); +s32 PS4_SYSV_ABI LibraryTermStub(void* library); +s32 PS4_SYSV_ABI LibrarySupportStub(void* library, u32 formats); +s32 PS4_SYSV_ABI LibraryOpenFontMemoryStub(void* library, u32 mode, const void* fontAddress, + u32 fontSize, u32 subFontIndex, u32 uniqueWord, + void** inoutFontObj); +s32 PS4_SYSV_ABI LibraryCloseFontObjStub(void* fontObj, u32 flags); +s32 PS4_SYSV_ABI LibraryGetFaceMetricStub(void* fontObj, u32 metricId, u16* outMetric); +s32 PS4_SYSV_ABI LibraryGetFaceScaleStub(void* fontObj, u16* outUnitsPerEm, float* outScale); +s32 PS4_SYSV_ABI LibraryGetGlyphIndexStub(void* fontObj, u32 codepoint_u16, u32* out_glyph_index); + +s32 PS4_SYSV_ABI LibrarySetCharSizeWithDpiStub(void* fontObj, u32 dpi_x, u32 dpi_y, float scale_x, + float scale_y, float* out_scale_x, + float* out_scale_y); +s32 PS4_SYSV_ABI LibrarySetCharSizeDefaultDpiStub(void* fontObj, float scale_x, float scale_y, + float* out_scale_x, float* out_scale_y); +s32 PS4_SYSV_ABI LibraryComputeLayoutBlockStub(void* fontObj, const void* style_state_block, + u8 (*out_words)[16]); +s32 PS4_SYSV_ABI LibraryComputeLayoutAltBlockStub(void* fontObj, const void* style_state_block, + u8 (*out_words)[16]); + +s32 PS4_SYSV_ABI LibraryLoadGlyphCachedStub(void* fontObj, u32 glyphIndex, s32 mode, + std::uint64_t* out_words); +s32 PS4_SYSV_ABI LibraryGetGlyphMetricsStub(void* fontObj, std::uint32_t* opt_param2, + std::uint8_t mode, std::uint8_t* out_params, + Libraries::Font::OrbisFontGlyphMetrics* out_metrics); +s32 PS4_SYSV_ABI LibraryApplyGlyphAdjustStub(void* fontObj, u32 p2, u32 glyphIndex, s32 p4, s32 p5, + u32* inoutGlyphIndex); +s32 PS4_SYSV_ABI LibraryConfigureGlyphStub(void* fontObj, std::uint32_t* in_params, s32 mode, + std::uint32_t* inout_state); + +s32 PS4_SYSV_ABI FtRendererCreate(void* renderer); +u64 PS4_SYSV_ABI FtRendererQuery(void* renderer, u8* params, s64* out_ptr, u8 (*out_vec)[16]); +s32 PS4_SYSV_ABI FtRendererDestroy(void* renderer); + +} // namespace Libraries::FontFt::Internal diff --git a/src/core/libraries/libs.cpp b/src/core/libraries/libs.cpp index 762753fd1..a82a2f3cc 100644 --- a/src/core/libraries/libs.cpp +++ b/src/core/libraries/libs.cpp @@ -12,6 +12,8 @@ #include "core/libraries/companion/companion_httpd.h" #include "core/libraries/companion/companion_util.h" #include "core/libraries/disc_map/disc_map.h" +#include "core/libraries/font/font.h" +#include "core/libraries/font/fontft.h" #include "core/libraries/game_live_streaming/gamelivestreaming.h" #include "core/libraries/gnmdriver/gnmdriver.h" #include "core/libraries/hmd/hmd.h" @@ -151,6 +153,8 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) { Libraries::Voice::RegisterLib(sym); Libraries::Rudp::RegisterLib(sym); Libraries::VrTracker::RegisterLib(sym); + Libraries::Font::RegisterlibSceFont(sym); + Libraries::FontFt::RegisterlibSceFontFt(sym); // Loading libSceSsl is locked behind a title workaround that currently applies to nothing. // Libraries::Ssl::RegisterLib(sym);