From e9ca20b65d28c42d7bb079eb386486c4276bcccd Mon Sep 17 00:00:00 2001 From: w1naenator Date: Thu, 8 Jan 2026 03:08:29 +0200 Subject: [PATCH] Add internal font handling structures and layout computation functions This commit introduces a new header file `fontft_internal.h` containing various structures and functions for managing font handles, layout metrics, and rendering operations. Key additions include: - Definitions for `FontHandleOpenInfo`, `FontHandleStyleStateTail`, and `FontHandleOpaqueNative` to encapsulate font state and properties. - Layout-related structures such as `HorizontalLayoutBlocks`, `VerticalLayoutBlocks`, and their respective I/O structures for managing layout metrics and effects. - Functions for computing horizontal and vertical layout blocks, along with utility functions for handling layout words in byte format. - Stubs for library functions related to font management and rendering. These changes aim to enhance the font rendering capabilities of the shadPS4 Emulator Project, providing a robust foundation for future font-related features. --- CMakeLists.txt | 6 +- externals/CMakeLists.txt | 10 + src/core/libraries/font/font.cpp | 6552 +++++++++++++------ src/core/libraries/font/font.h | 176 +- src/core/libraries/font/font_internal.cpp | 1932 ++++++ src/core/libraries/font/font_internal.h | 760 +++ src/core/libraries/font/fontft.cpp | 106 +- src/core/libraries/font/fontft.h | 7 +- src/core/libraries/font/fontft_internal.cpp | 3672 +++++++++++ src/core/libraries/font/fontft_internal.h | 603 ++ 10 files changed, 11887 insertions(+), 1937 deletions(-) create mode 100644 src/core/libraries/font/font_internal.cpp create mode 100644 src/core/libraries/font/font_internal.h create mode 100644 src/core/libraries/font/fontft_internal.cpp create mode 100644 src/core/libraries/font/fontft_internal.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 99aca5268..c89ccc90b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -468,8 +468,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 ) @@ -1085,7 +1089,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 libusb::usb lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json miniz fdk-aac) diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 8e96f9bec..c73201e7a 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/src/core/libraries/font/font.cpp b/src/core/libraries/font/font.cpp index 00a6ebd35..6083a5187 100644 --- a/src/core/libraries/font/font.cpp +++ b/src/core/libraries/font/font.cpp @@ -2,6 +2,8 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include +#include #include #include #include @@ -19,30 +21,35 @@ #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 "common/singleton.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 "font_error.h" -#define STBTT_STATIC -#define STB_TRUETYPE_IMPLEMENTATION -#include "externals/dear_imgui/imstb_truetype.h" - -namespace Libraries::Font { -struct FontLibOpaque; -} +#ifdef formatParams +#undef formatParams +#endif namespace { -Core::FileSys::MntPoints* g_mnt = Common::Singleton::Instance(); - using Libraries::Font::OrbisFontGenerateGlyphParams; using Libraries::Font::OrbisFontGlyph; using Libraries::Font::OrbisFontGlyphMetrics; @@ -51,49 +58,67 @@ using Libraries::Font::OrbisFontGlyphOutline; using Libraries::Font::OrbisFontGlyphOutlinePoint; using Libraries::Font::OrbisFontMem; using Libraries::Font::OrbisFontStyleFrame; +namespace Internal = Libraries::Font::Internal; -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 NamedParam { + std::string_view name; + std::string value; }; -struct FontState { - 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; - u32 dpi_x = 72; - u32 dpi_y = 72; - u32 font_set_type = 0; - std::filesystem::path system_font_path; - Libraries::Font::OrbisFontLib library = nullptr; - bool ext_face_ready = false; - bool is_otf_cff = false; - std::vector ext_face_data; - stbtt_fontinfo ext_face{}; - float ext_scale_for_height = 0.0f; - int ext_ascent = 0, ext_descent = 0, ext_lineGap = 0; - std::unordered_map ext_cache; - std::vector scratch; - bool logged_ext_use = false; - // Cached layout (PS4-style) for GetHorizontalLayout - float cached_baseline_y = 0.0f; - bool layout_cached = false; - // Renderer binding state (PS4 requires a bound renderer for render-scale queries) - Libraries::Font::OrbisFontRenderer bound_renderer = nullptr; - // Mark when the game asked for PS4 internal/system fonts (via OpenFontSet) - bool system_requested = false; -}; +template +static 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) { + if constexpr (std::is_same_v || std::is_same_v) { + return value ? fmt::format("\"{}\"", value) : "(null)"; + } + 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 { + return fmt::format("{}", value); + } +} -static std::unordered_map g_font_state; +template +static NamedParam Param(std::string_view name, const T& value) { + return NamedParam{name, DescribeValue(value)}; +} + +static std::string formatParams(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); +} + +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); + +using FontAllocFn = void*(PS4_SYSV_ABI*)(void* object, u32 size); +using FontFreeFn = void(PS4_SYSV_ABI*)(void* object, void* p); struct LibraryState { bool support_system = false; @@ -103,153 +128,50 @@ struct LibraryState { const Libraries::Font::OrbisFontMem* backing_memory = nullptr; Libraries::Font::OrbisFontLibCreateParams create_params = nullptr; u64 edition = 0; - Libraries::Font::FontLibOpaque* native = nullptr; + 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; void* owned_device_cache = nullptr; u32 owned_device_cache_size = 0; }; static std::unordered_map g_library_state; -static std::unordered_map - g_style_for_surface; - -static std::once_flag g_system_font_once; -static bool g_system_font_available = false; -static std::vector g_system_font_bytes; -static std::filesystem::path g_system_font_path; - -static bool EnsureSystemFontBlob(); -static bool HasTrueTypeGlyf(const std::vector& bytes); -static FontState* TryGetState(Libraries::Font::OrbisFontHandle h); -static std::string ReportSystemFaceRequest(FontState& st, Libraries::Font::OrbisFontHandle handle, - bool& attached_out); - -struct GeneratedGlyph { - OrbisFontGlyphOpaque glyph{}; - OrbisFontGlyphMetrics metrics{}; - u32 codepoint = 0; - Libraries::Font::OrbisFontHandle owner_handle{}; - OrbisFontGlyphOutline outline{}; - std::vector outline_points; - std::vector outline_tags; - std::vector outline_contours; - bool outline_initialized = false; -}; - -static std::mutex g_generated_glyph_mutex; -static std::unordered_set g_generated_glyphs; - -static void TrackGeneratedGlyph(OrbisFontGlyph glyph) { - std::scoped_lock lock(g_generated_glyph_mutex); - g_generated_glyphs.insert(glyph); -} - -static bool ForgetGeneratedGlyph(OrbisFontGlyph glyph) { - std::scoped_lock lock(g_generated_glyph_mutex); - return g_generated_glyphs.erase(glyph) > 0; -} - -static void BuildBoundingOutline(GeneratedGlyph& gg) { - const float left = gg.metrics.h_bearing_x; - const float top = gg.metrics.h_bearing_y; - const float right = gg.metrics.h_bearing_x + gg.metrics.w; - const float bottom = gg.metrics.h_bearing_y - gg.metrics.h; - - 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); // on-curve flags - 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; -} - -static bool BuildTrueOutline(GeneratedGlyph& gg) { - const auto* st = TryGetState(gg.owner_handle); - if (!st || !st->ext_face_ready) { - return false; +static 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; } - auto* face = &st->ext_face; - const int glyph_index = stbtt_FindGlyphIndex(face, static_cast(gg.codepoint)); - if (glyph_index <= 0) { - return false; - } - - stbtt_vertex* verts = nullptr; - const int count = stbtt_GetGlyphShape(face, glyph_index, &verts); - if (count <= 0 || !verts) { - return false; - } - - gg.outline_points.clear(); - gg.outline_tags.clear(); - gg.outline_contours.clear(); - - const float scale = st->ext_scale_for_height == 0.0f - ? stbtt_ScaleForPixelHeight(face, st->scale_h) - : st->ext_scale_for_height; - - auto push_point = [&](float x, float y, u8 tag) { - gg.outline_points.push_back({x * scale, y * scale}); - gg.outline_tags.push_back(tag); - }; - - int last_start = -1; - for (int i = 0; i < count; ++i) { - const stbtt_vertex& v = verts[i]; - switch (v.type) { - case STBTT_vmove: - if (last_start >= 0 && !gg.outline_points.empty()) { - gg.outline_contours.push_back(static_cast(gg.outline_points.size() - 1)); - } - last_start = static_cast(gg.outline_points.size()); - push_point(static_cast(v.x), static_cast(v.y), 1); - break; - case STBTT_vline: - push_point(static_cast(v.x), static_cast(v.y), 1); - break; - case STBTT_vcurve: - push_point(static_cast(v.cx), static_cast(v.cy), 0); - push_point(static_cast(v.x), static_cast(v.y), 1); - break; - case STBTT_vcubic: - // Approximate cubic with two off-curve controls then on-curve end. - push_point(static_cast(v.cx), static_cast(v.cy), 0); - push_point(static_cast(v.cx1), static_cast(v.cy1), 0); - push_point(static_cast(v.x), static_cast(v.y), 1); - break; - default: - break; - } - } - - if (last_start >= 0 && !gg.outline_points.empty()) { - gg.outline_contours.push_back(static_cast(gg.outline_points.size() - 1)); - } - - const bool ok = !gg.outline_points.empty() && !gg.outline_contours.empty(); - if (ok) { - 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; - } - - stbtt_FreeShape(face, verts); - return ok; } static u16 ClampToU16(float value) { @@ -260,28 +182,28 @@ static u16 ClampToU16(float value) { return static_cast(std::lround(clamped)); } -static FontState& GetState(Libraries::Font::OrbisFontHandle h) { - return g_font_state[h]; -} - -static FontState* TryGetState(Libraries::Font::OrbisFontHandle h) { - if (!h) - return nullptr; - auto it = g_font_state.find(h); - if (it == g_font_state.end()) - return nullptr; - return &it->second; -} - static LibraryState& GetLibState(Libraries::Font::OrbisFontLib lib) { return g_library_state[lib]; } static void RemoveLibState(Libraries::Font::OrbisFontLib lib) { if (auto it = g_library_state.find(lib); it != g_library_state.end()) { - if (it->second.owned_device_cache) { - delete[] static_cast(it->second.owned_device_cache); - } + const auto free_fn = it->second.free_fn; + const auto alloc_ctx = it->second.alloc_ctx; + auto free_owned = [&](void* p) { + if (!p) { + return; + } + if (free_fn) { + free_fn(alloc_ctx, p); + return; + } + std::free(p); + }; + free_owned(it->second.owned_device_cache); + free_owned(it->second.owned_external_fonts_ctx); + free_owned(it->second.owned_sysfonts_ctx); + free_owned(it->second.owned_mspace); g_library_state.erase(it); } } @@ -292,6 +214,90 @@ static void LogExternalFormatSupport(u32 formats_mask) { static bool LoadFontFile(const std::filesystem::path& path, std::vector& out_bytes); +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 void LogFontOpenParams(const Libraries::Font::OrbisFontOpenParams* params) { if (!params) { LOG_INFO(Lib_Font, "OpenFontSetParams: "); @@ -306,16 +312,7 @@ static void LogFontOpenParams(const Libraries::Font::OrbisFontOpenParams* params } static 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) { - return {}; - } - return g_mnt->GetHostPath(guest_path); + return Internal::ResolveGuestPath(guest_path); } static bool LoadGuestFileBytes(const std::filesystem::path& host_path, @@ -345,78 +342,9 @@ static bool LoadGuestFileBytes(const std::filesystem::path& host_path, return true; } -// External face scale helpers. -// - Point scaling (sceFontSetScalePoint): match stbtt_ScaleForPixelHeight semantics. -// - Pixel scaling (sceFontSetScalePixel): match stbtt_ScaleForMappingEmToPixels / FreeType EM. -static float ComputeScaleExtPoint(const stbtt_fontinfo* face, float pixel_h) { - if (!face) - return 0.0f; - return stbtt_ScaleForPixelHeight(face, pixel_h); -} - -static float ComputeScaleExtPixel(const stbtt_fontinfo* face, float pixel_h) { - if (!face) - return 0.0f; - return stbtt_ScaleForMappingEmToPixels(face, pixel_h); -} - -// Heuristic: identify external TrueType fonts that expect EM-based mapping -// (stbtt_ScaleForMappingEmToPixels) rather than ascender-height mapping. -// This is derived from observed ascent/descent/lineGap and blob sizes, but -// does not depend on the calling title. -static bool IsEmProfileExternalFont(const FontState& st) { - if (!st.ext_face_ready || st.ext_face_data.empty()) { - return false; - } - const std::size_t bytes = st.ext_face_data.size(); - const int asc = st.ext_ascent; - const int desc = st.ext_descent; - const int gap = st.ext_lineGap; - if (gap != 0) { - return false; - } - // Fonts observed in logs: - // - 81968 bytes, ascent=1989, descent=-469 - // - 3534360 bytes, ascent=901, descent=-123 - // - 1868660 bytes, ascent=800, descent=-200 - if (bytes == 81968 && asc == 1989 && desc == -469) { - return true; - } - if (bytes == 3534360 && asc == 901 && desc == -123) { - return true; - } - if (bytes == 1868660 && asc == 800 && desc == -200) { - return true; - } - return false; -} - -// Per-font-state external scale: -// - System fonts (font sets / fallbacks): ascender-height mapping (PixelHeight). -// - External CFF / point APIs: ascender-height mapping. -// - External TrueType + pixel scaling for certain profiles: EM-based mapping. -static float ComputeScaleExtForState(const FontState& st, const stbtt_fontinfo* face, - float pixel_h) { - if (!face) - return 0.0f; - const bool is_system_font = (st.font_set_type != 0) || !st.system_font_path.empty(); - if (is_system_font) { - return ComputeScaleExtPoint(face, pixel_h); - } - if (st.is_otf_cff || st.scale_point_active) { - return ComputeScaleExtPoint(face, pixel_h); - } - // Only a small subset of external TrueType fonts (Driveclub) require - // EM-based mapping; all others stay on ascender-height mapping to - // avoid regressions (Metaphor, etc.). - if (IsEmProfileExternalFont(st)) { - return ComputeScaleExtPixel(face, pixel_h); - } - return ComputeScaleExtPoint(face, pixel_h); -} - static constexpr float kPointsPerInch = 72.0f; static constexpr float kScaleEpsilon = 1e-4f; +constexpr u16 kStyleFrameMagic = 0x0F09; static float SafeDpiToFloat(u32 dpi) { return dpi == 0 ? kPointsPerInch : static_cast(dpi); @@ -431,123 +359,67 @@ static float PixelsToPoints(float px, u32 dpi) { return px * kPointsPerInch / dpi_f; } -constexpr u16 kStyleFrameMagic = 0x0F09; -constexpr u16 kStyleFrameFlagScale = 1 << 0; -constexpr u16 kStyleFrameFlagWeight = 1 << 1; -constexpr u16 kStyleFrameFlagSlant = 1 << 2; -constexpr u16 kStyleFrameFlagDpi = 1 << 3; - -enum class StyleFrameScalingMode : s32 { - None = 0, - Point = 1, - Pixel = 2, -}; - struct StyleFrameScaleState { float scale_w; float scale_h; u32 dpi_x; u32 dpi_y; - bool scale_overridden; - bool dpi_overridden; }; static StyleFrameScaleState ResolveStyleFrameScale(const OrbisFontStyleFrame* style, - const FontState& st) { + float base_scale_w, float base_scale_h, + u32 base_dpi_x, u32 base_dpi_y) { 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, + .scale_w = base_scale_w, + .scale_h = base_scale_h, + .dpi_x = base_dpi_x, + .dpi_y = base_dpi_y, }; if (!style || style->magic != kStyleFrameMagic) { return resolved; } - if ((style->flags & kStyleFrameFlagDpi) != 0) { - if (style->dpiX > 0) { - resolved.dpi_x = static_cast(style->dpiX); - resolved.dpi_overridden = true; - } - if (style->dpiY > 0) { - resolved.dpi_y = static_cast(style->dpiY); - resolved.dpi_overridden = true; - } + if (style->hDpi != 0) { + resolved.dpi_x = style->hDpi; } - if ((style->flags & kStyleFrameFlagScale) != 0 && style->scaleWidth > 0.0f && - style->scaleHeight > 0.0f) { - const auto mode = static_cast(style->scalingFlag); - if (mode == StyleFrameScalingMode::Point) { - resolved.scale_w = PointsToPixels(style->scaleWidth, resolved.dpi_x); - resolved.scale_h = PointsToPixels(style->scaleHeight, resolved.dpi_y); - resolved.scale_overridden = true; - } else if (mode == StyleFrameScalingMode::Pixel) { - resolved.scale_w = style->scaleWidth; - resolved.scale_h = style->scaleHeight; - resolved.scale_overridden = true; - } + if (style->vDpi != 0) { + resolved.dpi_y = style->vDpi; + } + if ((style->flags1 & 1u) == 0) { + return resolved; + } + + 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; } -static void InitializeStyleFrame(OrbisFontStyleFrame& frame) { - frame.magic = kStyleFrameMagic; - frame.flags = 0; - frame.dpiX = 72; - frame.dpiY = 72; - frame.scalingFlag = static_cast(StyleFrameScalingMode::None); - frame.scaleWidth = 0.0f; - frame.scaleHeight = 0.0f; - frame.weightXScale = 1.0f; - frame.weightYScale = 1.0f; - frame.slantRatio = 0.0f; -} - -static bool EnsureStyleFrameInitialized(OrbisFontStyleFrame* frame) { - if (!frame) - return false; - if (frame->magic != kStyleFrameMagic) { - InitializeStyleFrame(*frame); - } - return true; -} - static bool ValidateStyleFramePtr(const OrbisFontStyleFrame* frame) { return frame && frame->magic == kStyleFrameMagic; } -static void UpdateCachedLayout(FontState& st) { - if (!st.ext_face_ready) { - return; - } - if (st.ext_scale_for_height == 0.0f) - st.ext_scale_for_height = ComputeScaleExtForState(st, &st.ext_face, st.scale_h); - int asc = 0, desc = 0, gap = 0; - stbtt_GetFontVMetrics(&st.ext_face, &asc, &desc, &gap); - const float scale = st.ext_scale_for_height; - st.cached_baseline_y = static_cast(asc) * scale; - st.layout_cached = true; +static inline u32 LoadU32(const void* base, std::size_t off) { + u32 v = 0; + std::memcpy(&v, static_cast(base) + off, sizeof(v)); + return v; } - -static inline std::uint64_t MakeGlyphKey(u32 code, int pixel_h) { - return (static_cast(code) << 32) | static_cast(pixel_h); +static inline float LoadF32(const void* base, std::size_t off) { + float v = 0.0f; + std::memcpy(&v, static_cast(base) + off, sizeof(v)); + return v; } -static std::unordered_set g_stride_logged; -static inline void LogStrideOnce(const Libraries::Font::OrbisFontRenderSurface* surf) { - if (!surf) - return; - const void* key = static_cast(surf); - if (g_stride_logged.insert(key).second) { - const int bpp = std::max(1, static_cast(surf->pixelSizeByte)); - const long expected = static_cast(surf->width) * bpp; - const bool match = (expected == surf->widthByte); - LOG_INFO(Lib_Font, - "StrideCheck: surf={} buf={} width={} height={} pixelSizeByte={} widthByte={} " - "expected={} match={}", - key, surf->buffer, surf->width, surf->height, bpp, surf->widthByte, expected, - match); - } +static inline void StoreU32(void* base, std::size_t off, u32 v) { + std::memcpy(static_cast(base) + off, &v, sizeof(v)); +} +static inline void StoreF32(void* base, std::size_t off, float v) { + std::memcpy(static_cast(base) + off, &v, sizeof(v)); } static void ClearRenderOutputs(Libraries::Font::OrbisFontGlyphMetrics* metrics, @@ -560,64 +432,10 @@ static void ClearRenderOutputs(Libraries::Font::OrbisFontGlyphMetrics* metrics, } } -static const GlyphEntry* GetGlyphEntry(FontState& st, Libraries::Font::OrbisFontHandle handle, - u32 code, const stbtt_fontinfo*& face_out, - float& scale_out) { - face_out = nullptr; - scale_out = 0.0f; - if (st.ext_face_ready) { - face_out = &st.ext_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_face; - } - } - if (!face_out) { - return nullptr; - } - if (st.ext_scale_for_height == 0.0f) { - st.ext_scale_for_height = ComputeScaleExtForState(st, face_out, st.scale_h); - } - scale_out = st.ext_scale_for_height; - const int pixel_h = std::max(1, static_cast(std::lround(st.scale_h))); - 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) { - GlyphEntry entry{}; - int aw = 0, lsb = 0; - stbtt_GetCodepointHMetrics(face_out, static_cast(code), &aw, &lsb); - stbtt_GetCodepointBitmapBox(face_out, static_cast(code), scale_out, scale_out, - &entry.x0, &entry.y0, &entry.x1, &entry.y1); - entry.w = std::max(0, entry.x1 - entry.x0); - entry.h = std::max(0, entry.y1 - entry.y0); - entry.advance = static_cast(aw) * scale_out; - entry.bearingX = static_cast(lsb) * scale_out; - ge = &st.ext_cache.emplace(key, std::move(entry)).first->second; - } - return ge; -} - -// Font-set ID bit layout: -// bits [31:28] family (0x18 = SST Standard UI) -// bits [27:24] region (0x0 Latin, 0x8 Japanese, 0xC Chinese, 0xD Korean, 0xE Thai, 0xF -// Asian-mix) bits [23:20] variant (0x0 European, 0x2 Typewriter, 0x4 JP, 0x7 EU JPCJK, 0x8 JP -// UH, 0xC GB UH) bits [19:16] coverage (0x0 base, 0x4 Arabic, 0x5 Vietnamese, 0x6 Thai, 0xA CJK -// TH, 0xB full-set) bits [15:8] language tag (0x44 JP, 0x53 VI, 0x54 TH, 0x80 GB, 0xA CJK TH, 0xA4 -// JP CJK, 0xC4 JP AR) bits [7:0] weight/style (0x43 light, 0x44 regular, 0x45 medium, 0x47 bold, -// 0x53 light VI, 0x57 bold VI, 0xC7 Arabic bold) struct SystemFontDefinition { u32 font_set_type; - const char* config_key; // legacy UPPER_SNAKE (e.g., FONTSET_SST_STD_EUROPEAN_LIGHT) - const char* default_file; // default filename within sys font dir + const char* config_key; + const char* default_file; }; static constexpr SystemFontDefinition kSystemFontDefinitions[] = { @@ -731,7 +549,6 @@ static const SystemFontDefinition* FindSystemFontDefinition(u32 font_set_type) { } static std::filesystem::path GetSysFontBaseDir() { - // Only use the configured path. No variations. std::filesystem::path base = Config::getSysFontPath(); std::error_code ec; if (base.empty()) { @@ -754,19 +571,16 @@ static std::string MacroToCamel(const char* macro_key) { return {}; } std::string s(macro_key); - // Expect prefix "FONTSET_" const std::string prefix = "FONTSET_"; if (s.rfind(prefix, 0) != 0) { return {}; } std::string out = "FontSet"; - // Split on '_' size_t pos = prefix.size(); while (pos < s.size()) { size_t next = s.find('_', pos); const size_t len = (next == std::string::npos) ? (s.size() - pos) : (next - pos); std::string token = s.substr(pos, len); - // Lowercase then capitalize first char for (auto& c : token) { c = static_cast(std::tolower(static_cast(c))); } @@ -796,11 +610,7 @@ static std::filesystem::path ResolveSystemFontPath(u32 font_set_type) { LOG_ERROR(Lib_Font, "SystemFonts: override for '{}' must be a filename only (no path): '{}'", def->config_key, override_path->string()); - // ignore invalid override; fall back to default filename } - // Also support new-style keys: - // - UpperCamelCase: 'FontSetSstStdEuropeanLight' - // - lowerCamelCase: 'fontSetSstStdEuropeanLight' const auto camel_key = MacroToCamel(def->config_key); if (!camel_key.empty()) { if (auto override_path2 = Config::getSystemFontOverride(camel_key)) { @@ -843,55 +653,17 @@ static const FontSetCache* EnsureFontSetCache(u32 font_set_type) { cache.loaded = true; const auto path = ResolveSystemFontPath(font_set_type); if (!path.empty() && LoadFontFile(path, cache.bytes)) { - if (!HasTrueTypeGlyf(cache.bytes)) { - LOG_WARNING(Lib_Font, - "SystemFonts: '{}' lacks 'glyf' table (likely CFF) -> falling back", - path.string()); - cache.bytes.clear(); - cache.available = false; - } else { - cache.available = true; - cache.path = path; - } + cache.available = true; + cache.path = 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()); } - if (!cache.available) { - if (EnsureSystemFontBlob()) { - cache.bytes = g_system_font_bytes; - cache.path = g_system_font_path; - cache.available = true; - LOG_WARNING(Lib_Font, "SystemFonts: using fallback font '{}' for type 0x{:08X}", - cache.path.string(), font_set_type); - } - } } return cache.available ? &cache : nullptr; } -static bool HasTrueTypeGlyf(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; - } - const u8* dir = ptr + 12; - for (u32 i = 0; i < num_tables; ++i) { - const u8* entry = dir + i * kDirEntrySize; - const u32 tag = (entry[0] << 24) | (entry[1] << 16) | (entry[2] << 8) | entry[3]; - if (tag == (('g' << 24) | ('l' << 16) | ('y' << 8) | 'f')) { - return true; - } - } - return false; -} - static bool LoadFontFile(const std::filesystem::path& path, std::vector& out_bytes) { if (path.empty()) { return false; @@ -902,125 +674,6 @@ static bool LoadFontFile(const std::filesystem::path& path, std::vector* source_bytes = nullptr; - std::filesystem::path source_path; - if (cache) { - source_bytes = &cache->bytes; - source_path = cache->path; - } else { - if (!EnsureSystemFontBlob()) { - return false; - } - source_bytes = &g_system_font_bytes; - source_path = g_system_font_path; - } - if (!source_bytes || source_bytes->empty()) { - return false; - } - st.ext_face_data = *source_bytes; - st.ext_cache.clear(); - st.ext_scale_for_height = 0.0f; - st.layout_cached = false; - st.logged_ext_use = true; // skip "external (game font)" log for built-in fallback - const int offset = stbtt_GetFontOffsetForIndex(st.ext_face_data.data(), 0); - if (offset < 0) { - LOG_ERROR(Lib_Font, "SystemFace: invalid font offset in '{}'", source_path.string()); - st.ext_face_data.clear(); - st.ext_face_ready = false; - return false; - } - if (stbtt_InitFont(&st.ext_face, st.ext_face_data.data(), offset) == 0) { - LOG_ERROR(Lib_Font, "SystemFace: stbtt_InitFont failed for '{}'", source_path.string()); - st.ext_face_data.clear(); - st.ext_face_ready = false; - return false; - } - st.ext_face_ready = true; - st.system_font_path = source_path; - LOG_INFO(Lib_Font, "system font attached"); - LOG_DEBUG(Lib_Font, - "system font attach params:\n" - " handle={}\n" - " path={}\n", - static_cast(handle), source_path.string()); - return true; -} - -static 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::getSysFontPath(); - return fmt::format("SystemFace: handle={} requested internal font but sysFontPath ('{}') " - "could not be loaded", - static_cast(handle), configured.string()); - } - return {}; -} } // namespace namespace Libraries::Font { @@ -1036,7 +689,10 @@ struct FontLibOpaque { u8 reserved2[0x50]; void* sys_driver; void* fontset_registry; - u8 reserved3[0x10]; + union { + u8 reserved3[0x10]; + std::array sysfonts_ctx_name_overrides; + }; void* sysfonts_ctx; void* external_fonts_ctx; u32* device_cache_buf; @@ -1052,106 +708,465 @@ 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"); -struct OrbisFontRenderer_ {}; -static void* g_allocator_vtbl_stub[4] = {}; -static std::uint8_t g_sys_driver_stub{}; -static std::uint8_t g_fontset_registry_stub{}; -static std::uint8_t g_sysfonts_ctx_stub{}; -static std::uint8_t g_external_fonts_ctx_stub{}; -static u32 g_device_cache_stub{}; +#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*/ OrbisFontHandle list_head; + /*0x30*/ u8 reserved_30[0x88]; +}; +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"); + +#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"); + +namespace { +static FontHandleOpaqueNative* GetNativeFont(OrbisFontHandle h); + +static void LogCachedStyleOnce(OrbisFontHandle handle, const 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); +} + +static void LogRenderResultSample(OrbisFontHandle handle, u32 code, + const OrbisFontGlyphMetrics& metrics, + const 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); +} + +static inline u8 CachedStyleCacheFlags(const OrbisFontStyleFrame& cached_style) { + return static_cast(cached_style.cache_flags_and_direction & 0xFFu); +} + +static inline void CachedStyleSetCacheFlags(OrbisFontStyleFrame& cached_style, u8 flags) { + cached_style.cache_flags_and_direction = + (cached_style.cache_flags_and_direction & 0xFFFFFF00u) | static_cast(flags); +} + +static inline void CachedStyleSetDirectionWord(OrbisFontStyleFrame& cached_style, u16 word) { + cached_style.cache_flags_and_direction = + (cached_style.cache_flags_and_direction & 0x0000FFFFu) | (static_cast(word) << 16); +} + +static inline float CachedStyleGetScalar(const OrbisFontStyleFrame& cached_style) { + float value = 0.0f; + std::memcpy(&value, &cached_style.cached_scalar_bits, sizeof(value)); + return value; +} + +static inline void CachedStyleSetScalar(OrbisFontStyleFrame& cached_style, float value) { + std::memcpy(&cached_style.cached_scalar_bits, &value, sizeof(value)); +} + +constexpr std::uintptr_t kFtRendererCreateFnAddr = 0x0100f6d0u; +constexpr std::uintptr_t kFtRendererDestroyFnAddr = 0x0100f790u; + +static 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; +} + +static 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; +} + +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; +} +static FontHandleOpaqueNative* GetNativeFont(OrbisFontHandle h) { + return h ? reinterpret_cast(h) : nullptr; +} + +static bool AcquireFontLock(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); + } +} + +static void ReleaseFontLock(FontHandleOpaqueNative* font, u32 prev_lock_word) { + if (!font) { + return; + } + font->lock_word = prev_lock_word & 0x7fffffff; +} + +static bool AcquireCachedStyleLock(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); + } +} + +static void ReleaseCachedStyleLock(FontHandleOpaqueNative* font, u32 prev_lock_word) { + if (!font) { + return; + } + font->cached_style.cache_lock_word = prev_lock_word & 0x7fffffff; +} +} // namespace + +namespace {} + +struct OrbisFontRenderer_ { + u16 magic = 0; + u16 reserved0 = 0; +}; +static_assert(offsetof(OrbisFontRenderer_, magic) == 0, "Renderer magic offset"); 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"); + 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 (bad magic)"); + LOG_ERROR(Lib_Font, "INVALID_LIBRARY"); return ORBIS_FONT_ERROR_INVALID_LIBRARY; } - constexpr u32 kMinCacheSize = 0x1020u; - if (size < kMinCacheSize) { - LOG_WARNING(Lib_Font, "AttachDeviceCacheBuffer: size {} too small (min {})", size, - kMinCacheSize); - LOG_ERROR(Lib_Font, "size too small"); - return ORBIS_FONT_ERROR_INVALID_PARAMETER; - } - if (lib->device_cache_buf && lib->device_cache_buf != &g_device_cache_stub) { - LOG_WARNING(Lib_Font, "AttachDeviceCacheBuffer: library={} already attached", - static_cast(library)); - LOG_ERROR(Lib_Font, "already attached"); - return ORBIS_FONT_ERROR_ALREADY_ATTACHED; + + u32 prev_lock_word = 0; + if (!AcquireLibraryLock(lib, prev_lock_word)) { + LOG_ERROR(Lib_Font, "INVALID_LIBRARY"); + return ORBIS_FONT_ERROR_INVALID_LIBRARY; } - std::uint8_t* owned_buffer = nullptr; - if (!buffer) { - owned_buffer = new (std::nothrow) std::uint8_t[size]; - if (!owned_buffer) { - LOG_ERROR(Lib_Font, "allocation failed"); - return ORBIS_FONT_ERROR_ALLOCATION_FAILED; + 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; + } } - buffer = owned_buffer; + Libraries::Kernel::sceKernelUsleep(0x1e); } - auto* header = static_cast(buffer); - header[0] = size; - const u32 usable_bytes = size > 0x1000 ? size - 0x1000 : 0; - const u32 page_count = usable_bytes >> 12; - if (page_count == 0) { - if (owned_buffer) { - delete[] owned_buffer; - } - LOG_ERROR(Lib_Font, "page_count == 0"); - return ORBIS_FONT_ERROR_INVALID_PARAMETER; - } - header[1] = page_count; - header[2] = 0; - header[3] = page_count; - if (size >= 0x20) { - auto* meta = reinterpret_cast(header + 4); - *meta = 0x000FF800001000ULL; - } + using AllocFn = void*(PS4_SYSV_ABI*)(void* object, u32 size); + using FreeFn = void(PS4_SYSV_ABI*)(void* object, void* p); + 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; - lib->device_cache_buf = header; - auto& state = GetLibState(library); - if (owned_buffer) { - state.owned_device_cache = owned_buffer; - state.owned_device_cache_size = size; - lib->flags |= 1u; + 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 { - state.owned_device_cache = nullptr; - state.owned_device_cache_size = 0; + 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; + } + } } - LOG_INFO(Lib_Font, "device cache attach requested"); - LOG_DEBUG(Lib_Font, - "device cache attach params:\n" - " library={}\n" - " buffer={}\n" - " size={}\n" - " pages={}\n" - " owned_buffer={}\n", - static_cast(library), buffer, size, page_count, - owned_buffer ? "true" : "false"); - return ORBIS_OK; + 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(OrbisFontHandle fontHandle, OrbisFontRenderer renderer) { - if (!fontHandle || !renderer) { - LOG_ERROR(Lib_Font, "invalid parameter"); - return ORBIS_FONT_ERROR_INVALID_PARAMETER; - } - auto& st = GetState(fontHandle); - st.bound_renderer = renderer; LOG_INFO(Lib_Font, "called"); - LOG_DEBUG(Lib_Font, - "parameters:\n" - " handle={}\n" - " renderer={}\n", - static_cast(fontHandle), static_cast(renderer)); - return ORBIS_OK; + 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; + + LogCachedStyleOnce(fontHandle, *font); + + if (auto* st = Internal::TryGetState(fontHandle)) { + st->bound_renderer = renderer; + st->dpi_x = font->style_frame[0]; + st->dpi_y = font->style_frame[1]; + if (scale_unit != 0) { + st->render_scale_point_active = true; + st->render_scale_point_w = scale_w; + st->render_scale_point_h = scale_h; + } else { + st->render_scale_point_active = false; + st->render_scale_w = scale_w; + st->render_scale_h = scale_h; + } + } + } + } + + 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, @@ -1215,7 +1230,7 @@ sceFontCharacterRefersTextBack(OrbisFontTextCharacter* textCharacter) { OrbisFontTextCharacter* current = textCharacter->prev; while (current) { - if (current->unkn_0x31 == 0 && current->unkn_0x33 == 0) { + if (current->unknown_0x31 == 0 && current->unknown_0x33 == 0) { return current; } current = current->prev; @@ -1231,7 +1246,7 @@ sceFontCharacterRefersTextNext(OrbisFontTextCharacter* textCharacter) { OrbisFontTextCharacter* current = textCharacter->next; while (current) { - if (current->unkn_0x31 == 0 && current->unkn_0x33 == 0) { + if (current->unknown_0x31 == 0 && current->unknown_0x33 == 0) { return current; } current = current->next; @@ -1284,65 +1299,190 @@ s32 PS4_SYSV_ABI sceFontCreateLibrary(const OrbisFontMem* memory, s32 PS4_SYSV_ABI sceFontCreateLibraryWithEdition(const OrbisFontMem* memory, OrbisFontLibCreateParams create_params, u64 edition, OrbisFontLib* pLibrary) { - LOG_INFO(Lib_Font, "library create requested"); - LOG_DEBUG(Lib_Font, - "library create params:\n" - " memory={}\n" - " create_params={}\n" - " edition={}\n" - " out_library_ptr={}\n", - static_cast(memory), static_cast(create_params), edition, - static_cast(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) { - return ORBIS_FONT_ERROR_INVALID_PARAMETER; + 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; } - auto* lib = new (std::nothrow) FontLibOpaque{}; - if (!lib) { + if (!create_params || !pLibrary) { + LOG_ERROR(Lib_Font, "INVALID_PARAMETER"); + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + + using MallocFn = void*(PS4_SYSV_ABI*)(void* object, u32 size); + using FreeFn = void(PS4_SYSV_ABI*)(void* object, void* p); + 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(FontLibOpaque) + offsetof(FontLibOpaque, alloc_vtbl)); + clear_20(sizeof(FontLibOpaque) + offsetof(FontLibOpaque, flags)); + clear_20(offsetof(FontLibOpaque, sysfonts_ctx)); + clear_20(offsetof(FontLibOpaque, sys_driver)); + clear_20(offsetof(FontLibOpaque, reserved2) + 0x30); + clear_20(offsetof(FontLibOpaque, reserved2) + 0x10); + clear_20(offsetof(FontLibOpaque, alloc_ctx)); + clear_20(offsetof(FontLibOpaque, reserved3) + 0x0C); + clear_20(offsetof(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; - lib->lock_word = 0; - lib->flags = 0; - lib->alloc_ctx = const_cast(memory); - lib->alloc_vtbl = g_allocator_vtbl_stub; - lib->sys_driver = &g_sys_driver_stub; - lib->fontset_registry = &g_fontset_registry_stub; - lib->sysfonts_ctx = nullptr; - lib->external_fonts_ctx = nullptr; - lib->device_cache_buf = nullptr; + *pLibrary = lib; auto& state = GetLibState(lib); state = LibraryState{}; state.backing_memory = memory; state.create_params = create_params; state.edition = edition; - state.native = lib; - state.owned_device_cache = nullptr; - state.owned_device_cache_size = 0; + 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 sceFontDestroyLibrary(OrbisFontLib* pLibrary) { - if (!pLibrary || !*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); - delete static_cast(lib); + + using FreeFn = void(PS4_SYSV_ABI*)(void* object, void* p); + 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; } @@ -1356,31 +1496,138 @@ s32 PS4_SYSV_ABI sceFontCreateRenderer(const OrbisFontMem* memory, s32 PS4_SYSV_ABI sceFontCreateRendererWithEdition(const OrbisFontMem* memory, OrbisFontRendererCreateParams create_params, u64 edition, OrbisFontRenderer* pRenderer) { - LOG_INFO(Lib_Font, "renderer create requested"); - LOG_DEBUG(Lib_Font, - "renderer create params:\n" - " memory={}\n" - " create_params={}\n" - " edition={}\n" - " out_renderer_ptr={}\n", - static_cast(memory), static_cast(create_params), edition, - static_cast(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) { + using AllocFn = void*(PS4_SYSV_ABI*)(void* object, u32 size); + using FreeFn = void(PS4_SYSV_ABI*)(void* object, void* p); + 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); + using CreateFn = s32(PS4_SYSV_ABI*)(void* renderer); + CreateFn create_fn = reinterpret_cast(create_fn_ptr); + if (create_fn_ptr == kFtRendererCreateFnAddr) { + create_fn = &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) { - return ORBIS_FONT_ERROR_INVALID_PARAMETER; + LOG_ERROR(Lib_Font, "FAILED"); + return rc; } - if (!memory) { - return ORBIS_FONT_ERROR_INVALID_MEMORY; + *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"); } - auto* renderer = new (std::nothrow) OrbisFontRenderer_{}; - if (!renderer) { - return ORBIS_FONT_ERROR_ALLOCATION_FAILED; - } - *pRenderer = renderer; - LOG_INFO(Lib_Font, "CreateRenderer: memory={} selection={} edition={} renderer={}", - static_cast(memory), static_cast(create_params), edition, - static_cast(renderer)); - return ORBIS_OK; + return rc; } s32 PS4_SYSV_ABI sceFontCreateString() { @@ -1424,13 +1671,12 @@ s32 PS4_SYSV_ABI sceFontDeleteGlyph(const OrbisFontMem* memory, OrbisFontGlyph* return ORBIS_FONT_ERROR_INVALID_GLYPH; } - // We currently own the allocation; the memory pointer is only recorded for ABI compatibility. (void)memory; - if (!ForgetGeneratedGlyph(handle)) { + if (!Internal::ForgetGeneratedGlyph(handle)) { return ORBIS_FONT_ERROR_INVALID_GLYPH; } - auto* boxed = reinterpret_cast(handle); + auto* boxed = reinterpret_cast(handle); delete boxed; *glyph = nullptr; return ORBIS_OK; @@ -1447,19 +1693,62 @@ s32 PS4_SYSV_ABI sceFontDestroyGraphicsService() { } s32 PS4_SYSV_ABI sceFontDestroyRenderer(OrbisFontRenderer* pRenderer) { - if (!pRenderer || !*pRenderer) { + if (!pRenderer) { return ORBIS_FONT_ERROR_INVALID_PARAMETER; } - auto renderer = *pRenderer; - LOG_INFO(Lib_Font, "renderer destroy requested"); - LOG_DEBUG(Lib_Font, - "renderer destroy params:\n" - " renderer_ptr={}\n" - " renderer={}\n", - static_cast(pRenderer), static_cast(renderer)); - delete static_cast(renderer); - *pRenderer = nullptr; - 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); + using DestroyFn = s32(PS4_SYSV_ABI*)(void* renderer); + DestroyFn destroy_fn = reinterpret_cast(destroy_fn_ptr); + if (destroy_fn_ptr == kFtRendererDestroyFnAddr) { + destroy_fn = &FtRendererDestroy; + } + rc = destroy_fn ? destroy_fn(renderer) : ORBIS_FONT_ERROR_FATAL; + } + + renderer->selection = nullptr; + + using FreeFn = void(PS4_SYSV_ABI*)(void* object, void* p); + 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() { @@ -1515,7 +1804,7 @@ s32 PS4_SYSV_ABI sceFontGenerateCharGlyph(OrbisFontHandle glyph_handle, u32 code return ORBIS_FONT_ERROR_NO_SUPPORT_CODE; } - auto* st = TryGetState(glyph_handle); + auto* st = Internal::TryGetState(glyph_handle); if (!st) { return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; } @@ -1547,7 +1836,7 @@ s32 PS4_SYSV_ABI sceFontGenerateCharGlyph(OrbisFontHandle glyph_handle, u32 code return metrics_res; } - auto* boxed = new (std::nothrow) GeneratedGlyph(); + auto* boxed = new (std::nothrow) Libraries::Font::Internal::GeneratedGlyph(); if (!boxed) { return ORBIS_FONT_ERROR_ALLOCATION_FAILED; } @@ -1559,8 +1848,8 @@ s32 PS4_SYSV_ABI sceFontGenerateCharGlyph(OrbisFontHandle glyph_handle, u32 code 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.h_bearing_y); - boxed->glyph.height_px = ClampToU16(metrics.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; @@ -1576,7 +1865,7 @@ s32 PS4_SYSV_ABI sceFontGenerateCharGlyph(OrbisFontHandle glyph_handle, u32 code boxed->outline.points_cnt = 0; boxed->outline_initialized = false; - TrackGeneratedGlyph(&boxed->glyph); + Internal::TrackGeneratedGlyph(&boxed->glyph); *glyph_out = &boxed->glyph; return ORBIS_OK; } @@ -1593,98 +1882,118 @@ s32 PS4_SYSV_ABI sceFontGetCharGlyphCode() { s32 PS4_SYSV_ABI sceFontGetCharGlyphMetrics(OrbisFontHandle fontHandle, u32 code, OrbisFontGlyphMetrics* metrics) { - if (!metrics) { - LOG_ERROR(Lib_Font, "invalid metrics pointer"); - return ORBIS_FONT_ERROR_INVALID_PARAMETER; - } - auto& st = GetState(fontHandle); - const stbtt_fontinfo* face = nullptr; - float scale = 0.0f; + LOG_INFO(Lib_Font, "called"); - if (st.ext_face_ready) { - if (st.ext_scale_for_height == 0.0f) - st.ext_scale_for_height = stbtt_ScaleForPixelHeight(&st.ext_face, st.scale_h); - const int glyph = stbtt_FindGlyphIndex(&st.ext_face, static_cast(code)); - if (glyph > 0) { - face = &st.ext_face; - scale = st.ext_scale_for_height; - if (!st.logged_ext_use) { - LOG_INFO(Lib_Font, "RenderFace: handle={} source=external (game font)", - static_cast(fontHandle)); - st.logged_ext_use = true; - } - } - } + LOG_DEBUG(Lib_Font, "{}", + formatParams({ + Param("fontHandle", fontHandle), + Param("code", code), + Param("metrics", metrics), + })); - if (!face) { - bool system_attached = false; - const std::string sys_log = ReportSystemFaceRequest(st, fontHandle, system_attached); - if (!sys_log.empty()) { - LOG_ERROR(Lib_Font, "{}", sys_log); - } - if (system_attached) { - face = &st.ext_face; - if (st.ext_scale_for_height == 0.0f) - st.ext_scale_for_height = ComputeScaleExtForState(st, &st.ext_face, st.scale_h); - scale = st.ext_scale_for_height; - } + const s32 rc = Internal::GetCharGlyphMetrics(fontHandle, code, metrics, false); + if (rc != ORBIS_OK) { + LOG_ERROR(Lib_Font, "FAILED"); } - - if (face) { - const int pixel_h = std::max(1, (int)std::lround(st.scale_h)); - 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) { - GlyphEntry entry{}; - int aw = 0, lsb = 0; - stbtt_GetCodepointHMetrics(face, static_cast(code), &aw, &lsb); - stbtt_GetCodepointBitmapBox(face, static_cast(code), scale, scale, &entry.x0, - &entry.y0, &entry.x1, &entry.y1); - entry.w = std::max(0, entry.x1 - entry.x0); - entry.h = std::max(0, entry.y1 - entry.y0); - entry.advance = static_cast(aw) * scale; - entry.bearingX = static_cast(lsb) * scale; - ge = &st.ext_cache.emplace(key, std::move(entry)).first->second; - } - metrics->w = ge->w > 0 ? static_cast(ge->w) : st.scale_w; - metrics->h = ge->h > 0 ? static_cast(ge->h) : st.scale_h; - metrics->h_bearing_x = ge->bearingX; - metrics->h_bearing_y = static_cast(-ge->y0); - metrics->h_adv = ge->advance > 0.0f ? ge->advance : st.scale_w; - metrics->v_bearing_x = 0.0f; - metrics->v_bearing_y = 0.0f; - metrics->v_adv = 0.0f; - LOG_TRACE(Lib_Font, - "GetCharGlyphMetrics: code=U+{:04X} src=external size=({}, {}) adv={} " - "bearing=({}, {}) box={}x{}", - code, st.scale_w, st.scale_h, metrics->h_adv, metrics->h_bearing_x, - metrics->h_bearing_y, metrics->w, metrics->h); - return ORBIS_OK; - } - metrics->w = st.scale_w; - metrics->h = st.scale_h; - metrics->h_bearing_x = 0.0f; - metrics->h_bearing_y = st.scale_h; - metrics->h_adv = st.scale_w; - metrics->v_bearing_x = 0.0f; - metrics->v_bearing_y = 0.0f; - metrics->v_adv = 0.0f; - LOG_TRACE(Lib_Font, "GetCharGlyphMetrics(fallback): code=U+{:04X} size=({}, {}) box={}x{}", - code, st.scale_w, st.scale_h, metrics->w, metrics->h); - return ORBIS_OK; + 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() { @@ -1719,48 +2028,61 @@ s32 PS4_SYSV_ABI sceFontGetGlyphExpandBufferState() { s32 PS4_SYSV_ABI sceFontGetHorizontalLayout(OrbisFontHandle fontHandle, OrbisFontHorizontalLayout* layout) { - if (!fontHandle || !layout) { - LOG_ERROR(Lib_Font, "invalid parameter"); - return ORBIS_FONT_ERROR_INVALID_PARAMETER; - } - auto* st_ptr = TryGetState(fontHandle); - if (!st_ptr) { - LOG_ERROR(Lib_Font, "invalid font handle"); + LOG_INFO(Lib_Font, "called"); + + if (!fontHandle) { + if (layout) { + *layout = {}; + } + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; } - auto& st = *st_ptr; - if (!st.ext_face_ready) { - bool system_attached = false; - const std::string sys_log = ReportSystemFaceRequest(st, fontHandle, system_attached); - if (!sys_log.empty()) { - LOG_ERROR(Lib_Font, "{}", sys_log); + + 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; } - if (st.ext_face_ready) { - if (st.ext_scale_for_height == 0.0f) - st.ext_scale_for_height = stbtt_ScaleForPixelHeight(&st.ext_face, st.scale_h); - int asc = 0, desc = 0, gap = 0; - stbtt_GetFontVMetrics(&st.ext_face, &asc, &desc, &gap); - const float scale = st.ext_scale_for_height; - layout->baselineOffset = static_cast(asc) * scale; - layout->lineAdvance = static_cast(asc - desc + gap) * scale; - layout->decorationExtent = 0.0f; - LOG_INFO( - Lib_Font, - "GetHorizontalLayout: handle={} ext_ready={} baselineOffset={} lineAdvance={} scale={}", - static_cast(fontHandle), st.ext_face_ready, layout->baselineOffset, - layout->lineAdvance, scale); + + 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(); return ORBIS_OK; } - layout->baselineOffset = st.scale_h * 0.8f; - layout->lineAdvance = st.scale_h; - layout->decorationExtent = 0.0f; - LOG_INFO( - Lib_Font, - "GetHorizontalLayout: fallback handle={} ext_ready={} baselineOffset={} lineAdvance={}", - static_cast(fontHandle), st.ext_face_ready, layout->baselineOffset, - layout->lineAdvance); - return ORBIS_OK; + + *layout = {}; + LOG_ERROR(Lib_Font, "FAILED"); + return rc; } s32 PS4_SYSV_ABI sceFontGetKerning(OrbisFontHandle fontHandle, u32 preCode, u32 code, @@ -1769,35 +2091,60 @@ s32 PS4_SYSV_ABI sceFontGetKerning(OrbisFontHandle fontHandle, u32 preCode, u32 LOG_DEBUG(Lib_Font, "invalid parameters"); return ORBIS_FONT_ERROR_INVALID_PARAMETER; } - auto& st = GetState(fontHandle); - if (!st.ext_face_ready) { + auto& st = Internal::GetState(fontHandle); + if (!st.ext_face_ready || !st.ext_ft_face) { bool system_attached = false; - const std::string sys_log = ReportSystemFaceRequest(st, fontHandle, system_attached); + const std::string sys_log = + Internal::ReportSystemFaceRequest(st, fontHandle, system_attached); if (!sys_log.empty()) { LOG_ERROR(Lib_Font, "{}", sys_log); } } - if (st.ext_face_ready) { - if (st.ext_scale_for_height == 0.0f) - st.ext_scale_for_height = stbtt_ScaleForPixelHeight(&st.ext_face, st.scale_h); - int g1 = stbtt_FindGlyphIndex(&st.ext_face, static_cast(preCode)); - int g2 = stbtt_FindGlyphIndex(&st.ext_face, static_cast(code)); - if (g1 > 0 && g2 > 0) { - const int kern = stbtt_GetCodepointKernAdvance(&st.ext_face, static_cast(preCode), - static_cast(code)); - const float kx = static_cast(kern) * st.ext_scale_for_height; - kerning->dx = kx; - kerning->dy = 0.0f; - kerning->px = 0.0f; - kerning->py = 0.0f; - LOG_TRACE(Lib_Font, "GetKerning: pre=U+{:04X} code=U+{:04X} dx={}", preCode, code, kx); - return ORBIS_OK; + + 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; } } - kerning->dx = 0.0f; - kerning->dy = 0.0f; - kerning->px = 0.0f; - kerning->py = 0.0f; + + 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; + LOG_TRACE(Lib_Font, "GetKerning: pre=U+{:04X} code=U+{:04X} dx={}", preCode, code, kx); + return ORBIS_OK; + } + kerning->offsetX = 0.0f; + kerning->offsetY = 0.0f; + kerning->positionX = 0.0f; + kerning->positionY = 0.0f; LOG_TRACE(Lib_Font, "GetKerning: pre=U+{:04X} code=U+{:04X} dx=0 (no face)", preCode, code); return ORBIS_OK; } @@ -1806,7 +2153,7 @@ s32 PS4_SYSV_ABI sceFontGetLibrary(OrbisFontHandle fontHandle, OrbisFontLib* pLi if (!pLibrary) { return ORBIS_FONT_ERROR_INVALID_PARAMETER; } - const auto& st = GetState(fontHandle); + const auto& st = Internal::GetState(fontHandle); *pLibrary = st.library; return ORBIS_OK; } @@ -1818,26 +2165,75 @@ s32 PS4_SYSV_ABI sceFontGetPixelResolution() { s32 PS4_SYSV_ABI sceFontGetRenderCharGlyphMetrics(OrbisFontHandle fontHandle, u32 codepoint, OrbisFontGlyphMetrics* out_metrics) { - if (!fontHandle || !out_metrics) { - return ORBIS_FONT_ERROR_INVALID_PARAMETER; + 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) { + LOG_ERROR(Lib_Font, "FAILED"); } - auto& st = GetState(fontHandle); - if (!st.bound_renderer) { - LOG_DEBUG(Lib_Font, "GetRenderCharGlyphMetrics: renderer not bound for handle={}", - static_cast(fontHandle)); - return ORBIS_FONT_ERROR_NOT_BOUND_RENDERER; - } - return sceFontGetCharGlyphMetrics(fontHandle, codepoint, out_metrics); + 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() { @@ -1847,32 +2243,56 @@ s32 PS4_SYSV_ABI sceFontGetRenderScaledKerning() { s32 PS4_SYSV_ABI sceFontGetRenderScalePixel(OrbisFontHandle fontHandle, float* out_w, float* out_h) { - if (!fontHandle || (!out_w && !out_h)) { - return ORBIS_FONT_ERROR_INVALID_PARAMETER; + 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; + } + } } - auto* st = TryGetState(fontHandle); - if (!st) { - return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + if (out_w) { + *out_w = 0.0f; } - if (out_w) - *out_w = st->scale_w; - if (out_h) - *out_h = st->scale_h; - if (!st->bound_renderer) { - LOG_DEBUG(Lib_Font, - "GetRenderScalePixel: no renderer bound; returning configured scale w={} h={}", - out_w ? *out_w : -1.0f, out_h ? *out_h : -1.0f); - } else { - LOG_DEBUG(Lib_Font, "GetRenderScalePixel: handle={} -> w={}, h={}", - static_cast(fontHandle), out_w ? *out_w : -1.0f, - out_h ? *out_h : -1.0f); + if (out_h) { + *out_h = 0.0f; } - return ORBIS_OK; + return rc; } s32 PS4_SYSV_ABI sceFontGetRenderScalePoint(OrbisFontHandle fontHandle, float* out_w, float* out_h) { - return sceFontGetScalePoint(fontHandle, out_w, 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() { @@ -1881,43 +2301,76 @@ s32 PS4_SYSV_ABI sceFontGetResolutionDpi() { } s32 PS4_SYSV_ABI sceFontGetScalePixel(OrbisFontHandle fontHandle, float* out_w, float* out_h) { - if (!fontHandle || (!out_w && !out_h)) { - LOG_DEBUG(Lib_Font, "invalid parameters"); - return ORBIS_FONT_ERROR_INVALID_PARAMETER; - } - auto* st = TryGetState(fontHandle); - if (!st) { + 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; } - if (out_w) - *out_w = st->scale_w; - if (out_h) - *out_h = st->scale_h; - LOG_DEBUG(Lib_Font, "GetScalePixel: handle={} -> w={}, h={}", - static_cast(fontHandle), out_w ? *out_w : -1.0f, out_h ? *out_h : -1.0f); - return ORBIS_OK; + + 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(OrbisFontHandle fontHandle, float* out_w, float* out_h) { - if (!fontHandle || (!out_w && !out_h)) { - LOG_DEBUG(Lib_Font, "invalid parameters"); - return ORBIS_FONT_ERROR_INVALID_PARAMETER; + 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; + } + } } - auto* st = TryGetState(fontHandle); - if (!st) { - return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + if (out_w) { + *out_w = 0.0f; } - const float default_w = PixelsToPoints(st->scale_w, st->dpi_x); - const float default_h = PixelsToPoints(st->scale_h, st->dpi_y); - const float point_w = st->scale_point_active ? st->scale_point_w : default_w; - const float point_h = st->scale_point_active ? st->scale_point_h : default_h; - if (out_w) - *out_w = point_w; - if (out_h) - *out_h = point_h; - LOG_DEBUG(Lib_Font, "GetScalePoint: handle={} -> w={}pt h={}pt (active={})", - static_cast(fontHandle), point_w, point_h, st->scale_point_active); - return ORBIS_OK; + if (out_h) { + *out_h = 0.0f; + } + return rc; } s32 PS4_SYSV_ABI sceFontGetScriptLanguage() { @@ -1932,31 +2385,34 @@ s32 PS4_SYSV_ABI sceFontGetTypographicDesign() { s32 PS4_SYSV_ABI sceFontGetVerticalLayout(OrbisFontHandle fontHandle, OrbisFontVerticalLayout* layout) { - if (!layout) { - return ORBIS_FONT_ERROR_INVALID_PARAMETER; - } - auto& st = GetState(fontHandle); - if (!st.ext_face_ready) { - bool system_attached = false; - const std::string sys_log = ReportSystemFaceRequest(st, fontHandle, system_attached); - if (!sys_log.empty()) { - LOG_ERROR(Lib_Font, "{}", sys_log); + 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 (st.ext_face_ready) { - if (st.ext_scale_for_height == 0.0f) - st.ext_scale_for_height = ComputeScaleExtForState(st, &st.ext_face, st.scale_h); - int asc = 0, desc = 0, gap = 0; - stbtt_GetFontVMetrics(&st.ext_face, &asc, &desc, &gap); - layout->baselineOffsetX = 0.0f; - layout->columnAdvance = static_cast(asc - desc + gap) * st.ext_scale_for_height; + + if (layout) { layout->decorationSpan = 0.0f; - return ORBIS_OK; + *reinterpret_cast(layout) = 0; } - layout->baselineOffsetX = 0.0f; - layout->columnAdvance = st.scale_h; - layout->decorationSpan = 0.0f; - return ORBIS_OK; + return rc; } s32 PS4_SYSV_ABI sceFontGlyphDefineAttribute() { @@ -2008,16 +2464,13 @@ OrbisFontGlyphOutline* PS4_SYSV_ABI sceFontGlyphRefersOutline(OrbisFontGlyph gly if (!glyph || glyph->magic != 0x0F03 || glyph->glyph_form != 1) { return nullptr; } - { - std::scoped_lock lock(g_generated_glyph_mutex); - if (g_generated_glyphs.find(glyph) == g_generated_glyphs.end()) { - return nullptr; - } + auto* boxed = Internal::TryGetGeneratedGlyph(glyph); + if (!boxed) { + return nullptr; } - auto* boxed = reinterpret_cast(glyph); if (!boxed->outline_initialized) { - if (!BuildTrueOutline(*boxed)) { - BuildBoundingOutline(*boxed); + if (!Internal::BuildTrueOutline(*boxed)) { + Internal::BuildBoundingOutline(*boxed); } } return &boxed->outline; @@ -2291,11 +2744,30 @@ 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) { + LOG_INFO(Lib_Font, "called"); + if (!mem_desc) { + LOG_ERROR(Lib_Font, "NULL_POINTER"); return ORBIS_FONT_ERROR_INVALID_PARAMETER; } - if (!iface && (!region_addr || region_size == 0)) { - 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; @@ -2308,19 +2780,6 @@ s32 PS4_SYSV_ABI sceFontMemoryInit(OrbisFontMem* mem_desc, void* region_addr, u3 mem_desc->destroy_ctx = destroy_ctx; mem_desc->some_ctx1 = nullptr; mem_desc->some_ctx2 = nullptr; - - LOG_INFO(Lib_Font, "font memory init requested"); - LOG_DEBUG(Lib_Font, - "font memory init params:\n" - " mem_desc={}\n" - " region_addr={}\n" - " region_size={}\n" - " mspace_obj={}\n" - " has_iface={}\n" - " destroy_cb={}\n", - static_cast(mem_desc), static_cast(region_addr), - region_size, static_cast(mspace_obj), iface != nullptr, - reinterpret_cast(destroy_cb)); return ORBIS_OK; } @@ -2331,354 +2790,2466 @@ s32 PS4_SYSV_ABI sceFontMemoryTerm(OrbisFontMem* mem_desc) { if (mem_desc->mem_kind != 0x0F00) { return ORBIS_FONT_ERROR_INVALID_MEMORY; } - if (mem_desc->on_destroy) { - mem_desc->on_destroy(mem_desc, mem_desc->destroy_ctx, mem_desc->some_ctx1); + + if (static_cast(mem_desc->attr_bits & 0xFF) < 0) { + if (mem_desc->iface && mem_desc->iface->mspace_destroy) { + using DestroyFn = void(PS4_SYSV_ABI*)(void* parent, void* mspace); + 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; + mem_desc->attr_bits = 0; + mem_desc->on_destroy(mem_desc, mem_desc->mspace_handle, mem_desc->destroy_ctx); + return ORBIS_OK; + } + std::memset(mem_desc, 0, sizeof(*mem_desc)); - LOG_INFO(Lib_Font, "font memory term requested"); - LOG_DEBUG(Lib_Font, - "font memory term params:\n" - " mem_desc={}\n", - static_cast(mem_desc)); return ORBIS_OK; } s32 PS4_SYSV_ABI sceFontOpenFontFile(OrbisFontLib library, const char* guest_path, u32 open_mode, const OrbisFontOpenParams* open_detail, OrbisFontHandle* out_handle) { - if (!library || !guest_path || !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; + + using AllocFn = void*(PS4_SYSV_ABI*)(void* object, u32 size); + using FreeFn = void(PS4_SYSV_ABI*)(void* object, void* p); + 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; - - std::vector file_bytes; - if (!LoadGuestFileBytes(path_to_open, file_bytes)) { - LOG_WARNING(Lib_Font, "OpenFontFile: failed to open '{}'", path_to_open.string()); - return ORBIS_FONT_ERROR_FS_OPEN_FAILED; - } - if (file_bytes.size() > std::numeric_limits::max()) { - LOG_WARNING(Lib_Font, "OpenFontFile: '{}' exceeds libSceFont size limit ({} bytes)", - path_to_open.string(), file_bytes.size()); - return ORBIS_FONT_ERROR_FS_OPEN_FAILED; - } - LOG_INFO(Lib_Font, "OpenFontFile: path='{}' size={} openMode={}", path_to_open.string(), - static_cast(file_bytes.size()), open_mode); - return sceFontOpenFontMemory(library, file_bytes.data(), static_cast(file_bytes.size()), - open_detail, out_handle); -} - -s32 PS4_SYSV_ABI sceFontOpenFontInstance(OrbisFontHandle fontHandle, OrbisFontHandle templateFont, - OrbisFontHandle* pFontHandle) { - if ((!templateFont && !fontHandle) || !pFontHandle) { - LOG_ERROR(Lib_Font, "invalid parameter base={} template={} out_ptr={}", - static_cast(fontHandle), static_cast(templateFont), - static_cast(pFontHandle)); + 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; } - LOG_INFO(Lib_Font, "called"); - LOG_DEBUG(Lib_Font, - "parameters:\n" - " base={}\n" - " template={}\n" - " out_ptr={}\n", - static_cast(fontHandle), static_cast(templateFont), - static_cast(pFontHandle)); - auto* src_state = TryGetState(templateFont); - if (!src_state) { - templateFont = nullptr; - src_state = TryGetState(fontHandle); + 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::g_font_state.erase(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; + } } - if (!src_state) { - LOG_ERROR(Lib_Font, "template handle={} missing", - templateFont ? static_cast(templateFont) - : static_cast(fontHandle)); - return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + + 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::g_font_state.erase(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); } - FontState* base_state = TryGetState(fontHandle); - if (!base_state && fontHandle) { - LOG_WARNING(Lib_Font, "base handle={} unknown, falling back to template state", - static_cast(fontHandle)); + + 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; } - auto* new_handle = new (std::nothrow) OrbisFontHandleOpaque{}; - if (!new_handle) { - LOG_ERROR(Lib_Font, "allocation failed"); - return ORBIS_FONT_ERROR_ALLOCATION_FAILED; + + 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; + } } - const std::size_t src_ext_bytes = src_state->ext_face_data.size(); - LOG_DEBUG(Lib_Font, - "template state:\n" - " ext_ready={}\n" - " bytes={}\n" - " cache_size={}\n" - " dpi_x={}\n" - " dpi_y={}\n" - " scale_w={}\n" - " scale_h={}\n" - " scale_point_active={}\n", - src_state->ext_face_ready, src_ext_bytes, src_state->ext_cache.size(), - src_state->dpi_x, src_state->dpi_y, src_state->scale_w, src_state->scale_h, - src_state->scale_point_active); - auto& dst = GetState(new_handle); - dst = *src_state; - dst.ext_face = {}; - dst.ext_cache.clear(); - dst.scratch.clear(); - dst.library = base_state ? base_state->library : src_state->library; - dst.bound_renderer = base_state ? base_state->bound_renderer : src_state->bound_renderer; - dst.logged_ext_use = false; - bool stbtt_ok = false; - if (dst.ext_face_ready && !dst.ext_face_data.empty()) { - const int font_offset = src_state->ext_face.fontstart; - LOG_INFO(Lib_Font, "reinitializing stbtt font for clone"); - LOG_DEBUG(Lib_Font, - "stbtt reinit params:\n" - " template_handle={}\n" - " new_handle={}\n" - " offset={}\n" - " data_size={}\n", - static_cast(templateFont), static_cast(new_handle), - font_offset, dst.ext_face_data.size()); - if (stbtt_InitFont(&dst.ext_face, dst.ext_face_data.data(), font_offset) == 0) { - LOG_WARNING(Lib_Font, "stbtt_InitFont failed during clone"); - dst.ext_face_ready = false; - dst.ext_cache.clear(); + + 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 { - dst.ext_scale_for_height = ComputeScaleExtForState(dst, &dst.ext_face, dst.scale_h); - stbtt_ok = true; + 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 { - LOG_DEBUG(Lib_Font, - "font instance clone missing external face:\n" - " template_handle={}\n" - " ready={}\n" - " data_size={}\n", - static_cast(templateFont), dst.ext_face_ready, - dst.ext_face_data.size()); - dst.ext_face_ready = false; + 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(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; + } + + using AllocFn = void*(PS4_SYSV_ABI*)(void* object, u32 size); + using FreeFn = void(PS4_SYSV_ABI*)(void* object, void* p); + 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::g_font_state.erase(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; + } + + struct IncRecord { + u32 entry_index; + Internal::FontObj* node; + }; + 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.entry_index >= max_entries || !inc.node) { + continue; + } + auto* entry = reinterpret_cast( + entries_base + inc.entry_index * 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.node->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::g_font_state.erase(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; } - dst.layout_cached = false; - *pFontHandle = new_handle; - LOG_INFO(Lib_Font, "font instance clone requested"); - LOG_DEBUG(Lib_Font, - "font instance clone result:\n" - " base_handle={}\n" - " template_handle={}\n" - " new_handle={}\n" - " ext_ready={}\n" - " renderer_inherited={}\n" - " stbtt_reinit={}\n" - " library={}\n" - " renderer={}\n" - " dpi_x={}\n" - " dpi_y={}\n" - " scale_w={}\n" - " scale_h={}\n", - static_cast(fontHandle), static_cast(templateFont), - static_cast(new_handle), dst.ext_face_ready, - dst.bound_renderer != nullptr, stbtt_ok, static_cast(dst.library), - static_cast(dst.bound_renderer), dst.dpi_x, dst.dpi_y, dst.scale_w, - dst.scale_h); return ORBIS_OK; } s32 PS4_SYSV_ABI sceFontOpenFontMemory(OrbisFontLib library, const void* fontAddress, u32 fontSize, const OrbisFontOpenParams* open_params, OrbisFontHandle* pFontHandle) { - if (!library || !fontAddress || fontSize == 0 || !pFontHandle) { - LOG_DEBUG(Lib_Font, "invalid parameters"); + 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; + } + + using AllocFn = void*(PS4_SYSV_ABI*)(void* object, u32 size); + using FreeFn = void(PS4_SYSV_ABI*)(void* object, void* p); + 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; } - (void)open_params; - auto* f = new OrbisFontHandleOpaque{}; - *pFontHandle = f; - auto& st = GetState(f); + + 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::g_font_state.erase(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::g_font_state.erase(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 unsigned char* p = reinterpret_cast(fontAddress); - auto& ls = GetLibState(library); - LOG_INFO(Lib_Font, "font memory open requested"); - LOG_DEBUG(Lib_Font, - "font memory open params:\n" - " library={}\n" - " font_address={}\n" - " font_size={}\n" - " open_params={}\n" - " out_handle={}\n" - " sig='{}{}{}{}'\n" - " external_supported={}\n" - " external_formats=0x{:X}", - static_cast(library), fontAddress, fontSize, - static_cast(open_params), static_cast(*pFontHandle), - (fontSize >= 1 ? (char)p[0] : '?'), (fontSize >= 2 ? (char)p[1] : '?'), - (fontSize >= 3 ? (char)p[2] : '?'), (fontSize >= 4 ? (char)p[3] : '?'), - ls.support_external, ls.external_formats); - st.ext_face_data.assign(reinterpret_cast(fontAddress), - reinterpret_cast(fontAddress) + fontSize); - int font_count = stbtt_GetNumberOfFonts(st.ext_face_data.data()); - int chosen_index = 0; - if (font_count > 1) { - chosen_index = 0; - if (open_params) { - chosen_index = - static_cast(open_params->subfont_index % static_cast(font_count)); - } + { + const u8* src = static_cast(fontAddress); + st.ext_face_data.assign(src, src + fontSize); } - int offset = stbtt_GetFontOffsetForIndex(st.ext_face_data.data(), chosen_index); - const unsigned char* d = st.ext_face_data.data(); - const u32 sig32 = (fontSize >= 4) - ? (static_cast(d[0]) << 24) | (static_cast(d[1]) << 16) | - (static_cast(d[2]) << 8) | static_cast(d[3]) - : 0u; - const bool is_ttc = (font_count > 1); - const bool is_otf_cff = (sig32 == 0x4F54544Fu); - const bool is_ttf_sfnt = (sig32 == 0x00010000u) || (sig32 == 0x74727565u); - const bool is_sfnt_typ1 = (sig32 == 0x74797031u); - st.is_otf_cff = is_otf_cff; - if (is_otf_cff) { - LOG_WARNING(Lib_Font, - "ExternalFace: OTF/CFF detected (OTTO). CFF outlines are not supported;" - " handle={} fonts={} requested_index={} -> fallback may occur", - static_cast(*pFontHandle), font_count, chosen_index); - } - if (stbtt_InitFont(&st.ext_face, st.ext_face_data.data(), offset)) { + + 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; - stbtt_GetFontVMetrics(&st.ext_face, &st.ext_ascent, &st.ext_descent, &st.ext_lineGap); - st.ext_scale_for_height = ComputeScaleExtForState(st, &st.ext_face, st.scale_h); - LOG_INFO(Lib_Font, "external face ready"); - LOG_DEBUG(Lib_Font, - "external face params:\n" - " handle={}\n" - " ascent={}\n" - " descent={}\n" - " line_gap={}\n" - " scale={}\n" - " data_bytes={}\n" - " font_count={}\n" - " chosen_index={}\n" - " ttc={}\n" - " sig=0x{:08X}\n" - " kind={}\n", - static_cast(*pFontHandle), st.ext_ascent, st.ext_descent, - st.ext_lineGap, st.ext_scale_for_height, (int)st.ext_face_data.size(), font_count, - chosen_index, is_ttc, sig32, - is_otf_cff ? "OTF/CFF (unsupported)" - : (is_ttf_sfnt ? "TTF (ready)" - : (is_sfnt_typ1 ? "Type1(sfnt) (stub)" : "unknown"))); - if (is_ttf_sfnt) { - LOG_INFO(Lib_Font, "external format TTF ready"); - } else if (is_otf_cff) { - LOG_WARNING(Lib_Font, "external format OpenType-CFF unsupported"); - } else if (is_sfnt_typ1) { - LOG_WARNING(Lib_Font, "external format Type1(sfnt) stub"); - } } else { - LOG_WARNING(Lib_Font, - "ExternalFace: stbtt_InitFont failed for handle={} size={} fonts={}" - " chosen_index={} sig='{}{}{}{}' (OTF/CFF unsupported={})", - static_cast(*pFontHandle), fontSize, font_count, chosen_index, - (fontSize >= 1 ? (char)p[0] : '?'), (fontSize >= 2 ? (char)p[1] : '?'), - (fontSize >= 3 ? (char)p[2] : '?'), (fontSize >= 4 ? (char)p[3] : '?'), - is_otf_cff); - if (is_otf_cff) { - LOG_WARNING(Lib_Font, "Stubbed: CFF outlines not implemented; system font unavailable"); - } + st.ext_face_ready = false; } + return ORBIS_OK; } s32 PS4_SYSV_ABI sceFontOpenFontSet(OrbisFontLib library, u32 fontSetType, u32 openMode, const OrbisFontOpenParams* open_params, OrbisFontHandle* pFontHandle) { - if (!library) { - return ORBIS_FONT_ERROR_INVALID_LIBRARY; - } - auto* lib = static_cast(library); - if (lib->magic != 0x0F01) { - return ORBIS_FONT_ERROR_INVALID_LIBRARY; - } - if (!pFontHandle) { - return ORBIS_FONT_ERROR_INVALID_PARAMETER; - } - if (!lib->sysfonts_ctx) { - return ORBIS_FONT_ERROR_NO_SUPPORT_FUNCTION; - } - auto* f = new OrbisFontHandleOpaque{}; - *pFontHandle = f; - auto& st = GetState(f); - st.library = library; - st.font_set_type = fontSetType; - st.system_font_path.clear(); + LOG_INFO(Lib_Font, "called"); - LOG_INFO(Lib_Font, "font set open requested"); - LOG_DEBUG(Lib_Font, - "font set open params:\n" - " library={}\n" - " font_set_type=0x{:08X}\n" - " open_mode={}\n" - " open_params={}\n" - " out_handle={}\n", - static_cast(library), fontSetType, openMode, - static_cast(open_params), static_cast(*pFontHandle)); + LOG_DEBUG(Lib_Font, "{}", + formatParams({ + Param("library", library), + Param("fontSetType", fontSetType), + Param("openMode", openMode), + Param("open_params", open_params), + Param("pFontHandle", pFontHandle), + })); - if (const auto* def = FindSystemFontDefinition(fontSetType)) { - const auto pretty = MacroToCamel(def->config_key); - LOG_DEBUG(Lib_Font, - "font set mapping:\n" - " type=0x{:08X}\n" - " key={}\n" - " default_file='{}'\n", - fontSetType, !pretty.empty() ? pretty.c_str() : def->config_key, - def->default_file ? def->default_file : ""); - } else { - LOG_DEBUG(Lib_Font, - "font set mapping:\n" - " type=0x{:08X}\n" - " key=(unknown)\n" - " default_file=''\n", - fontSetType); - } - LogFontOpenParams(open_params); - bool system_ok = false; - const std::string sys_log = ReportSystemFaceRequest(st, f, system_ok); - if (!sys_log.empty()) { - LOG_ERROR(Lib_Font, "{}", sys_log); - } + { + 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; + } - LOG_DEBUG(Lib_Font, - "font set open result:\n" - " library={}\n" - " font_set_type=0x{:08X}\n" - " open_mode={}\n" - " open_params={}\n" - " handle={}\n" - " system_available={}\n", - static_cast(library), fontSetType, openMode, - static_cast(open_params), static_cast(*pFontHandle), - system_ok); - return ORBIS_OK; + 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; + }; + + const u32 mode_low = openMode & 0x0Fu; + if (mode_low != 1 && mode_low != 2 && mode_low != 3) { + LOG_ERROR(Lib_Font, "INVALID_PARAMETER"); + return release_library_and_clear_out(ORBIS_FONT_ERROR_INVALID_PARAMETER); + } + 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); + } + + using AllocFn = void*(PS4_SYSV_ABI*)(void* object, u32 size); + using FreeFn = void(PS4_SYSV_ABI*)(void* object, void* p); + 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); + } + + if (!lib_local->sysfonts_ctx) { + constexpr u32 kSysCtxSize = 0x1020; + void* ctx = alloc_fn(lib_local->alloc_ctx, kSysCtxSize); + if (!ctx) { + LOG_ERROR(Lib_Font, "ALLOCATION_FAILED"); + return release_library_and_clear_out(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 = + reinterpret_cast(lib_local->sys_driver); + const auto support_fn = driver ? driver->support_formats : nullptr; + if (!support_fn) { + free_fn(lib_local->alloc_ctx, ctx); + LOG_ERROR(Lib_Font, "INVALID_LIBRARY"); + return release_library_and_clear_out(ORBIS_FONT_ERROR_INVALID_LIBRARY); + } + const s32 support_rc = support_fn(library, 0x52); + if (support_rc != ORBIS_OK) { + free_fn(lib_local->alloc_ctx, ctx); + LOG_ERROR(Lib_Font, "SUPPORT_FAILED"); + return release_library_and_clear_out(support_rc); + } + + lib_local->sysfonts_ctx = ctx; + auto& ls = GetLibState(library); + ls.support_system = true; + ls.owned_sysfonts_ctx = ctx; + ls.owned_sysfonts_ctx_size = kSysCtxSize; + } + + const u32 sub_font_index = open_params ? open_params->subfont_index : 0u; + const s32 unique_id = open_params ? open_params->unique_id : -1; + if (unique_id < -1) { + LOG_ERROR(Lib_Font, "INVALID_PARAMETER"); + return release_library_and_clear_out(ORBIS_FONT_ERROR_INVALID_PARAMETER); + } + + std::filesystem::path primary_path = + Internal::ResolveSystemFontPathFromConfigOnly(fontSetType); + if (primary_path.empty()) { + 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); + } + + std::vector primary_bytes; + if (!Internal::LoadFontFile(primary_path, primary_bytes)) { + LOG_ERROR(Lib_Font, "FONT_OPEN_FAILED path='{}' sysFontPath='{}'", + primary_path.string(), Config::getSysFontPath().string()); + return release_library_and_clear_out(ORBIS_FONT_ERROR_FONT_OPEN_FAILED); + } + + OrbisFontHandle handle = *pFontHandle; + if (handle) { + if (auto* h = GetNativeFont(handle)) { + h->flags = 0; + } + Internal::g_font_state.erase(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; + } + } + + 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::g_font_state.erase(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 = 0x0F02; + h->flags = static_cast(h->flags | static_cast(mode_low)); + 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; + *pFontHandle = 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; + } + + 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); + } + const Internal::FaceMetrics m = Internal::LoadFaceMetrics(st.ext_ft_face); + Internal::PopulateStateMetrics(st, m); + st.ext_face_ready = true; + + 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 resolve_override = + [&](const std::string& key) -> std::optional { + if (auto override_path = Config::getSystemFontOverride(key)) { + if (!override_path->empty() && override_path->is_absolute()) { + return *override_path; + } + if (!override_path->empty() && !override_path->has_parent_path()) { + return base_dir / *override_path; + } + LOG_ERROR(Lib_Font, + "SystemFonts: override for '{}' must be a filename only or absolute " + "path: '{}'", + key, override_path->string()); + } + 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) { + std::vector fb_bytes; + if (!Internal::LoadFontFile(p, fb_bytes)) { + return; + } + 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) { + st.system_fallback_faces.push_back(std::move(fb)); + } else { + Internal::DestroyFreeTypeFace(fb.ft_face); + } + }; + + { + if (auto roman_path = resolve_sysfont_path(base_dir / "SST-Roman.otf")) { + const std::string roman_lower = lower_ascii(roman_path->filename().string()); + if (!has_fallback_name_lower(roman_lower)) { + add_fallback_face(*roman_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; + 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); + } + } + } + } + + for (int i = 0; i < 8; ++i) { + const std::string key_a = + fmt::format("fontset_0x{:08X}_fallback{}", fontSetType, i); + const std::string key_b = + fmt::format("fontSet_0x{:08X}_fallback{}", fontSetType, i); + std::optional p = resolve_override(key_a); + if (!p) { + p = resolve_override(key_b); + } + if (!p || p->empty()) { + continue; + } + + std::optional p_resolved; + if (p->is_absolute()) { + std::error_code ec; + if (std::filesystem::exists(*p, ec)) { + p_resolved = *p; + } + } else { + p_resolved = resolve_sysfont_path(*p); + } + if (!p_resolved) { + continue; + } + + std::vector fb_bytes; + if (!Internal::LoadFontFile(*p_resolved, fb_bytes)) { + continue; + } + + Internal::FontState::SystemFallbackFace fb{}; + fb.font_id = 0xffffffffu; + fb.scale_factor = 1.0f; + fb.shift_value = 0; + fb.path = *p_resolved; + 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) { + st.system_fallback_faces.push_back(std::move(fb)); + } else { + Internal::DestroyFreeTypeFace(fb.ft_face); + } + } + + { + const std::string global_fallback_name = Config::getSystemFontFallbackName(); + if (!global_fallback_name.empty()) { + const auto existing_name = primary_path.filename().string(); + if (existing_name != global_fallback_name) { + std::filesystem::path fb_path = global_fallback_name; + if (!fb_path.is_absolute()) { + fb_path = base_dir / global_fallback_name; + } + if (const auto resolved_path = resolve_sysfont_path(fb_path)) { + const std::string fb_lower = + lower_ascii(resolved_path->filename().string()); + if (!has_fallback_name_lower(fb_lower)) { + add_fallback_face(*resolved_path); + } + } + } + } + } + } + + { + 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; + }; + + 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; + } + const s32 unique_id = stable_unique_id_for(host_path_str); + const u32 packed_unique_id = static_cast(unique_id) | 0x80000000u; + + 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; + + 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; + } + + 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; + } + } + } + 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) { + 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(), 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) { + 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; + } + 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; + h->open_info.ctx_entry_index = st.system_font_id; + font_ids.push_back(st.system_font_id); + + 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); + } + + st.fontset_selector = std::make_shared(); + 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; + }; + for (const auto& fb : st.system_fallback_faces) { + const auto name = lower_ascii_local(fb.path.filename().string()); + if (name == "sst-roman.otf" && fb.font_id != 0xffffffffu) { + st.fontset_selector->roman_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*); + + st.fontset_record_storage = std::make_shared>(rec_size); + 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; + } + + ReleaseLibraryLock(lib_local, prev_lib_lock); + return ORBIS_OK; + } } s32 PS4_SYSV_ABI sceFontRebindRenderer(OrbisFontHandle fontHandle) { if (!fontHandle) { - return ORBIS_FONT_ERROR_INVALID_PARAMETER; + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; } - auto& st = GetState(fontHandle); - if (!st.bound_renderer) { - return ORBIS_FONT_ERROR_NOT_BOUND_RENDERER; + + auto* font = GetNativeFont(fontHandle); + if (!font || font->magic != 0x0F02) { + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; } - LOG_INFO(Lib_Font, "RebindRenderer: handle={} renderer={}", - static_cast(fontHandle), static_cast(st.bound_renderer)); - return ORBIS_OK; + + u32 prev_font_lock = 0; + if (!AcquireFontLock(font, prev_font_lock)) { + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; + } + + u32 prev_cached_lock = 0; + if (!AcquireCachedStyleLock(font, prev_cached_lock)) { + ReleaseFontLock(font, prev_font_lock); + 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); + return rc; } s32 PS4_SYSV_ABI sceFontRenderCharGlyphImage(OrbisFontHandle fontHandle, u32 code, OrbisFontRenderSurface* surf, float x, float y, OrbisFontGlyphMetrics* metrics, OrbisFontRenderOutput* result) { - return sceFontRenderCharGlyphImageHorizontal(fontHandle, code, surf, x, y, metrics, 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 { + struct RenderSurfaceSystemUse { + OrbisFontStyleFrame* styleframe; + float catchedScale; + std::uint8_t padding[88 - sizeof(OrbisFontStyleFrame*) - sizeof(float)]; + }; + static_assert(sizeof(RenderSurfaceSystemUse) == + sizeof(((OrbisFontRenderSurface*)nullptr)->reserved_q)); + + auto* sys = reinterpret_cast(surf->reserved_q); + auto* styleframe = sys ? sys->styleframe : nullptr; + + if (!styleframe || (styleframe->flags1 & 1) == 0) { + (void)cache_cachedstyle_baseline(); + } else { + struct StyleStateBlock { + /*0x00*/ u32 dpi_x; + /*0x04*/ u32 dpi_y; + /*0x08*/ u32 vDpi_flag; + /*0x0C*/ u32 reserved0c; + /*0x10*/ float scale_x; + /*0x14*/ float scale_y; + /*0x18*/ float effect_wx; + /*0x1C*/ float effect_wy; + /*0x20*/ float slant; + }; + StyleStateBlock state{}; + state.dpi_x = styleframe->hDpi; + state.dpi_y = styleframe->vDpi; + state.vDpi_flag = 0; + state.reserved0c = 0; + state.effect_wx = 0.0f; + state.effect_wy = 0.0f; + state.slant = 0.0f; + if ((styleframe->flags1 & 2) != 0) { + state.slant = styleframe->slantRatio; + } + if ((styleframe->flags1 & 4) != 0) { + state.effect_wx = styleframe->effectWeightX; + state.effect_wy = styleframe->effectWeightY; + } + + pre_rc = Internal::StyleStateGetScalePixel(&styleframe->hDpi, &state.scale_x, + &state.scale_y); + 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; + + 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 { + struct RenderSurfaceSystemUse { + OrbisFontStyleFrame* styleframe; + float catchedScale; + std::uint8_t padding[88 - sizeof(OrbisFontStyleFrame*) - sizeof(float)]; + }; + static_assert(sizeof(RenderSurfaceSystemUse) == + sizeof(((OrbisFontRenderSurface*)nullptr)->reserved_q)); + + auto* sys = reinterpret_cast(surf->reserved_q); + auto* styleframe = sys ? sys->styleframe : nullptr; + if (!styleframe || (styleframe->flags1 & 1) == 0) { + (void)cache_cachedstyle_scalar(); + } else { + struct StyleStateBlock { + /*0x00*/ u32 dpi_x; + /*0x04*/ u32 dpi_y; + /*0x08*/ u32 vDpi_flag; + /*0x0C*/ u32 reserved0c; + /*0x10*/ float scale_x; + /*0x14*/ float scale_y; + /*0x18*/ float effect_wx; + /*0x1C*/ float effect_wy; + /*0x20*/ float slant; + }; + StyleStateBlock state{}; + state.dpi_x = styleframe->hDpi; + state.dpi_y = styleframe->vDpi; + state.vDpi_flag = 0; + state.reserved0c = 0; + state.effect_wx = 0.0f; + state.effect_wy = 0.0f; + state.slant = 0.0f; + if ((styleframe->flags1 & 2) != 0) { + state.slant = styleframe->slantRatio; + } + if ((styleframe->flags1 & 4) != 0) { + state.effect_wx = styleframe->effectWeightX; + state.effect_wy = styleframe->effectWeightY; + } + + pre_rc = Internal::StyleStateGetScalePixel(&styleframe->hDpi, &state.scale_x, + &state.scale_y); + 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 sceFontRenderCharGlyphImageHorizontal(OrbisFontHandle fontHandle, u32 code, @@ -2686,251 +5257,126 @@ s32 PS4_SYSV_ABI sceFontRenderCharGlyphImageHorizontal(OrbisFontHandle fontHandl float y, OrbisFontGlyphMetrics* metrics, OrbisFontRenderOutput* result) { LOG_INFO(Lib_Font, "called"); - LOG_DEBUG(Lib_Font, - "parameters:\n" - " handle={}\n" - " code=U+{:04X}\n" - " x={}\n" - " y={}\n" - " metrics_ptr={}\n" - " result_ptr={}\n" - " surf_ptr={}\n" - " buf={}\n" - " widthByte={}\n" - " pixelSizeByte={}\n" - " size={}x{}\n" - " scissor=[{},{}-{}:{}]\n" - " styleFlag={}\n", - static_cast(fontHandle), code, x, y, static_cast(metrics), - static_cast(result), static_cast(surf), - surf ? static_cast(surf->buffer) : nullptr, surf ? surf->widthByte : -1, - surf ? (int)surf->pixelSizeByte : -1, surf ? surf->width : -1, - surf ? surf->height : -1, surf ? surf->sc_x0 : 0u, surf ? surf->sc_y0 : 0u, - surf ? surf->sc_x1 : 0u, surf ? surf->sc_y1 : 0u, surf ? (int)surf->styleFlag : -1); - - LogStrideOnce(surf); if (!fontHandle) { ClearRenderOutputs(metrics, result); - LOG_ERROR(Lib_Font, "invalid font handle"); + 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"); + 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"); + LOG_ERROR(Lib_Font, "INVALID_PARAMETER"); return ORBIS_FONT_ERROR_INVALID_PARAMETER; } - auto* st = TryGetState(fontHandle); - if (!st) { + + 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 font handle (state)"); + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; } - if (!st->bound_renderer) { + + 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, "renderer not bound"); - return ORBIS_FONT_ERROR_NOT_BOUND_RENDERER; + LOG_ERROR(Lib_Font, "FAILED"); + return rc; } - - // Resolve effective pixel scales (style frame overrides -> point scales -> pixel scales) - const OrbisFontStyleFrame* style_frame = nullptr; - if ((surf->styleFlag & 0x1) != 0) { - if (auto it = g_style_for_surface.find(surf); it != g_style_for_surface.end()) { - style_frame = it->second; - } - } - StyleFrameScaleState style_scale = ResolveStyleFrameScale(style_frame, *st); - if (st->scale_point_active) { - style_scale.scale_w = PointsToPixels(st->scale_point_w, style_scale.dpi_x); - style_scale.scale_h = PointsToPixels(st->scale_point_h, style_scale.dpi_y); - } - - const stbtt_fontinfo* face = nullptr; - float scale_y = 0.0f; - if (st->ext_face_ready) { - face = &st->ext_face; - scale_y = ComputeScaleExtForState(*st, face, style_scale.scale_h); - } - if (!face) { - bool system_attached = false; - const std::string sys_log = ReportSystemFaceRequest(*st, fontHandle, system_attached); - if (!sys_log.empty()) { - LOG_ERROR(Lib_Font, "{}", sys_log); - } - if (system_attached) { - face = &st->ext_face; - scale_y = ComputeScaleExtForState(*st, face, style_scale.scale_h); - } - } - if (!face || scale_y <= kScaleEpsilon) { - ClearRenderOutputs(metrics, result); - LOG_ERROR(Lib_Font, "no support glyph (face/scale)"); - return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH; - } - const float scale_x = (style_scale.scale_h > kScaleEpsilon) - ? scale_y * (style_scale.scale_w / style_scale.scale_h) - : scale_y; - - const int glyph_index = stbtt_FindGlyphIndex(face, static_cast(code)); - if (glyph_index <= 0) { - ClearRenderOutputs(metrics, result); - return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH; - } - - int aw = 0, lsb = 0; - stbtt_GetCodepointHMetrics(face, static_cast(code), &aw, &lsb); - int x0 = 0, y0 = 0, x1 = 0, y1 = 0; - const float frac_x = x - std::floor(x); - const float frac_y = y - std::floor(y); - stbtt_GetCodepointBitmapBoxSubpixel(face, static_cast(code), scale_x, scale_y, frac_x, - frac_y, &x0, &y0, &x1, &y1); - const int glyph_w = std::max(0, x1 - x0); - const int glyph_h = std::max(0, y1 - y0); - - metrics->w = glyph_w > 0 ? static_cast(glyph_w) : style_scale.scale_w; - metrics->h = glyph_h > 0 ? static_cast(glyph_h) : style_scale.scale_h; - metrics->h_bearing_x = static_cast(lsb) * scale_x; - metrics->h_bearing_y = static_cast(-y0); - metrics->h_adv = static_cast(aw) * scale_x; - metrics->v_bearing_x = 0.0f; - metrics->v_bearing_y = 0.0f; - metrics->v_adv = 0.0f; - - // Compute the baseline position for the current scale so that we can - // place the glyph inside the render surface using the same convention as - // the original library: (x, y) specifies the top-left of the line box, - // while glyphs are positioned relative to the baseline. - int asc = 0, desc = 0, gap = 0; - stbtt_GetFontVMetrics(face, &asc, &desc, &gap); - const float baseline_from_top = static_cast(asc) * scale_y; - const float glyph_top_from_top = baseline_from_top - metrics->h_bearing_y; - - if (!surf->buffer || surf->width <= 0 || surf->height <= 0 || surf->widthByte <= 0 || - surf->pixelSizeByte <= 0) { - ClearRenderOutputs(metrics, result); - LOG_ERROR(Lib_Font, "no support surface (buffer)"); - return ORBIS_FONT_ERROR_NO_SUPPORT_SURFACE; - } - - const int bpp = static_cast(surf->pixelSizeByte); - if (bpp != 1 && bpp != 4) { - ClearRenderOutputs(metrics, result); - LOG_ERROR(Lib_Font, "no support surface (bpp={})", bpp); - return ORBIS_FONT_ERROR_NO_SUPPORT_SURFACE; - } - - std::vector glyph_bitmap; - glyph_bitmap.resize(static_cast(std::max(0, glyph_w)) * - static_cast(std::max(0, glyph_h))); - if (glyph_w > 0 && glyph_h > 0) { - stbtt_MakeCodepointBitmapSubpixel(face, glyph_bitmap.data(), glyph_w, glyph_h, glyph_w, - scale_x, scale_y, frac_x, frac_y, static_cast(code)); - } - - // Decide how to interpret (x, y) based on the font source: - // - System fonts (font sets like SST used by AnywhereVR) behaved best - // when (x, y) was treated as the raw top-left of the glyph box. - // - Game-supplied external TrueType fonts using pixel scaling for Driveclub - // match the original library when (x, y) is the baseline position. - // - Other external fonts (CFF / point scaling / non-Driveclub TTF) behave - // like Catherine_ok, where (x, y) is the line top and glyphs are placed - // from the baseline. - const bool is_system_font = (st->font_set_type != 0) || !st->system_font_path.empty(); - const bool use_baseline_ttf = !is_system_font && !st->is_otf_cff && !st->scale_point_active && - IsEmProfileExternalFont(*st); - - int dest_x = static_cast(std::floor(x)); - int dest_y = 0; - if (is_system_font) { - // AnywhereVR / system fonts: raw top-left. - dest_y = static_cast(std::floor(y)); - } else if (use_baseline_ttf) { - // Driveclub-style TrueType game fonts: (x, y) is the baseline. - const int baseline_dest_x = static_cast(std::floor(x)) + x0; - const int baseline_dest_y = static_cast(std::floor(y)) + y0; - dest_x = baseline_dest_x; - dest_y = baseline_dest_y; - } else { - // Catherine / Digimon / ReFantazio external fonts: - // (x, y) is line top, align glyph from baseline. - dest_y = static_cast(std::floor(y + glyph_top_from_top)); - } - // Apply surface scissor: rendering is clipped to [sc_x0, sc_x1) x [sc_y0, sc_y1), - // clamped to the surface bounds. - 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->slot.maybe_addr = static_cast(surf->buffer); - result->slot.maybe_rowBytes = static_cast(surf->widthByte); - result->slot.maybe_pixelSize = static_cast(surf->pixelSizeByte); - result->slot.maybe_pixelFmt = 0; - result->new_x = static_cast(std::max(update_x0, 0)); - result->new_y = static_cast(std::max(update_y0, 0)); - result->new_w = static_cast(std::max(update_w, 0)); - result->new_h = static_cast(std::max(update_h, 0)); - result->ImageMetrics.bearing_x = metrics->h_bearing_x; - result->ImageMetrics.bearing_y = metrics->h_bearing_y; - result->ImageMetrics.dv = metrics->h_adv; - result->ImageMetrics.stride = static_cast(surf->widthByte); - result->ImageMetrics.width = static_cast(std::max(update_w, 0)); - result->ImageMetrics.height = static_cast(std::max(update_h, 0)); + LogRenderResultSample(fontHandle, code, *metrics, *result); return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontRenderCharGlyphImageVertical() { - 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; } @@ -2953,98 +5399,123 @@ void PS4_SYSV_ABI sceFontRenderSurfaceInit(OrbisFontRenderSurface* renderSurface int bufWidthByte, int pixelSizeByte, int widthPixel, int heightPixel) { LOG_INFO(Lib_Font, "called"); - LOG_DEBUG(Lib_Font, - "parameters:\n" - " renderSurface={}\n" - " buffer={}\n" - " bufWidthByte={}\n" - " pixelSizeByte={}\n" - " widthPixel={}\n" - " heightPixel={}\n", - static_cast(renderSurface), buffer, bufWidthByte, pixelSizeByte, - widthPixel, heightPixel); - if (renderSurface) { - renderSurface->buffer = buffer; - renderSurface->widthByte = bufWidthByte; - renderSurface->pixelSizeByte = static_cast(pixelSizeByte); - renderSurface->pad0 = 0; - renderSurface->styleFlag = 0; - renderSurface->pad2 = 0; - renderSurface->width = (widthPixel < 0) ? 0 : widthPixel; - renderSurface->height = (heightPixel < 0) ? 0 : heightPixel; - renderSurface->sc_x0 = 0; - renderSurface->sc_y0 = 0; - renderSurface->sc_x1 = static_cast(renderSurface->width); - renderSurface->sc_y1 = static_cast(renderSurface->height); - std::fill(std::begin(renderSurface->reserved_q), std::end(renderSurface->reserved_q), 0); - LOG_DEBUG(Lib_Font, - "result:\n" - " buf={}\n" - " widthByte={}\n" - " pixelSizeByte={}\n" - " size={}x{}\n" - " scissor=[0,0-{}:{}]\n", - static_cast(buffer), bufWidthByte, pixelSizeByte, - renderSurface->width, renderSurface->height, renderSurface->sc_x1, - renderSurface->sc_y1); + + 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), + })); + + 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; } void PS4_SYSV_ABI sceFontRenderSurfaceSetScissor(OrbisFontRenderSurface* renderSurface, int x0, int y0, int w, int h) { LOG_INFO(Lib_Font, "called"); - LOG_DEBUG(Lib_Font, - "parameters:\n" - " renderSurface={}\n" - " x0={}\n" - " y0={}\n" - " w={}\n" - " h={}\n", - static_cast(renderSurface), x0, y0, w, h); - if (!renderSurface) + + if (!renderSurface) { + LOG_ERROR(Lib_Font, "NULL_POINTER"); return; + } - int surfaceWidth = renderSurface->width; - int clip_x0, clip_x1; + LOG_DEBUG(Lib_Font, "{}", + formatParams({ + Param("renderSurface", renderSurface), + Param("x0", x0), + Param("y0", y0), + Param("w", w), + Param("h", h), + })); - if (surfaceWidth != 0) { + 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) { - clip_x0 = 0; - clip_x1 = (w + x0 > surfaceWidth) ? surfaceWidth : w + x0; - if (w <= -x0) - clip_x1 = 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_x0 = (x0 > surfaceWidth) ? surfaceWidth : x0; - clip_x1 = (w + x0 > surfaceWidth) ? surfaceWidth : w + x0; + 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_x0 = static_cast(clip_x0); - renderSurface->sc_x1 = static_cast(clip_x1); + renderSurface->sc_x0 = x0_out; + renderSurface->sc_x1 = x1; } - int surfaceHeight = renderSurface->height; - int clip_y0, clip_y1; - - if (surfaceHeight != 0) { - if (y0 < 0) { - clip_y0 = 0; - clip_y1 = (h + y0 > surfaceHeight) ? surfaceHeight : h + y0; - if (h <= -y0) - clip_y1 = 0; - } else { - clip_y0 = (y0 > surfaceHeight) ? surfaceHeight : y0; - clip_y1 = (h + y0 > surfaceHeight) ? surfaceHeight : h + y0; - } - renderSurface->sc_y0 = static_cast(clip_y0); - renderSurface->sc_y1 = static_cast(clip_y1); + const u32 surface_h = static_cast(renderSurface->height); + if (surface_h == 0) { + return; } - LOG_DEBUG(Lib_Font, - "result:\n" - " sc_x0={}\n" - " sc_y0={}\n" - " sc_x1={}\n" - " sc_y1={}\n", - renderSurface->sc_x0, renderSurface->sc_y0, renderSurface->sc_x1, - renderSurface->sc_y1); + + 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, @@ -3052,34 +5523,81 @@ s32 PS4_SYSV_ABI sceFontRenderSurfaceSetStyleFrame(OrbisFontRenderSurface* rende if (!renderSurface) return ORBIS_FONT_ERROR_INVALID_PARAMETER; if (!styleFrame) { - g_style_for_surface.erase(renderSurface); renderSurface->styleFlag &= ~u8{0x1}; renderSurface->reserved_q[0] = 0; + renderSurface->reserved_q[1] = 0; LOG_INFO(Lib_Font, "RenderSurfaceSetStyleFrame: surf={} cleared", static_cast(renderSurface)); return ORBIS_OK; } - if (!EnsureStyleFrameInitialized(styleFrame)) + if (styleFrame->magic != kStyleFrameMagic) { return ORBIS_FONT_ERROR_INVALID_PARAMETER; - g_style_for_surface[renderSurface] = styleFrame; + } renderSurface->styleFlag |= 0x1; renderSurface->reserved_q[0] = reinterpret_cast(styleFrame); - LOG_INFO(Lib_Font, - "RenderSurfaceSetStyleFrame: surf={} styleFrame={} flags=0x{:X} scale=({}, {}) " - "dpi=({}, {}) mode={}", - static_cast(renderSurface), static_cast(styleFrame), - styleFrame->flags, styleFrame->scaleWidth, styleFrame->scaleHeight, styleFrame->dpiX, - styleFrame->dpiY, styleFrame->scalingFlag); + renderSurface->reserved_q[1] = 0; return ORBIS_OK; } -s32 PS4_SYSV_ABI sceFontSetEffectSlant() { - LOG_DEBUG(Lib_Font, "SetEffectSlant: no-op (effects not implemented)"); +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_DEBUG(Lib_Font, "SetEffectWeight: no-op (effects not implemented)"); +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; } @@ -3089,122 +5607,104 @@ s32 PS4_SYSV_ABI sceFontSetFontsOpenMode() { } s32 PS4_SYSV_ABI sceFontSetResolutionDpi(OrbisFontHandle fontHandle, u32 h_dpi, u32 v_dpi) { - if (!fontHandle) { - LOG_ERROR(Lib_Font, "invalid font handle (null)"); + auto* font = GetNativeFont(fontHandle); + if (!font) { return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; } - auto* st_ptr = TryGetState(fontHandle); - if (!st_ptr) { - LOG_ERROR(Lib_Font, "invalid font handle"); + + u32 prev_font_lock = 0; + if (!AcquireFontLock(font, prev_font_lock)) { return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; } - auto& st = *st_ptr; - const u32 new_h = h_dpi == 0 ? 0x48u : h_dpi; - const u32 new_v = v_dpi == 0 ? 0x48u : v_dpi; - if (st.dpi_x == new_h && st.dpi_y == new_v) { - LOG_TRACE(Lib_Font, "SetResolutionDpi: handle={} unchanged h_dpi={} v_dpi={}", - static_cast(fontHandle), new_h, new_v); - return ORBIS_OK; + + if (Internal::StyleStateSetDpi(font->style_frame, h_dpi, v_dpi) != 0) { + font->cached_style.layout_cache_state = 0; } - st.dpi_x = new_h; - st.dpi_y = new_v; - st.layout_cached = false; // PS4 clears cached metrics when the resolution changes. - LOG_INFO(Lib_Font, "resolution dpi set requested"); - LOG_DEBUG(Lib_Font, - "resolution dpi params:\n" - " handle={}\n" - " h_dpi={}\n" - " v_dpi={}\n", - static_cast(fontHandle), new_h, new_v); + + 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(OrbisFontHandle fontHandle, float w, float h) { - if (!fontHandle || w <= 0.0f || h <= 0.0f) { - LOG_ERROR(Lib_Font, "invalid parameter handle={} w={} h={}", - static_cast(fontHandle), w, h); - return ORBIS_FONT_ERROR_INVALID_PARAMETER; - } - auto* st_ptr = TryGetState(fontHandle); - if (!st_ptr) { - LOG_ERROR(Lib_Font, "invalid font handle={}", static_cast(fontHandle)); + LOG_INFO(Lib_Font, "called"); + + if (!fontHandle) { + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; } - auto& st = *st_ptr; - st.scale_point_active = false; - st.scale_w = w; - st.scale_h = h; - if (st.ext_face_ready) - st.ext_scale_for_height = ComputeScaleExtForState(st, &st.ext_face, st.scale_h); - bool system_attached = false; - const std::string sys_log = ReportSystemFaceRequest(st, fontHandle, system_attached); - if (!sys_log.empty()) { - LOG_ERROR(Lib_Font, "{}", sys_log); + + 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; } - LOG_INFO(Lib_Font, "scale pixel set requested"); - LOG_DEBUG(Lib_Font, - "scale pixel params:\n" - " handle={}\n" - " w={}\n" - " h={}\n" - " ext_scale={}\n" - " ext_ready={}\n", - static_cast(fontHandle), w, h, st.ext_scale_for_height, - st.ext_face_ready); - st.layout_cached = false; + + 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(OrbisFontHandle fontHandle, float w, float h) { - if (!fontHandle || w <= 0.0f || h <= 0.0f) { - LOG_ERROR(Lib_Font, "invalid parameter handle={} w={} h={}", - static_cast(fontHandle), w, h); - return ORBIS_FONT_ERROR_INVALID_PARAMETER; - } - auto* st_ptr = TryGetState(fontHandle); - if (!st_ptr) { - LOG_ERROR(Lib_Font, "invalid font handle={}", static_cast(fontHandle)); + LOG_INFO(Lib_Font, "called"); + + if (!fontHandle) { + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; } - auto& st = *st_ptr; - const float pixel_w = std::max(0.01f, PointsToPixels(w, st.dpi_x)); - const float pixel_h = std::max(0.01f, PointsToPixels(h, st.dpi_y)); - const bool unchanged_point = st.scale_point_active && - std::abs(st.scale_point_w - w) < kScaleEpsilon && - std::abs(st.scale_point_h - h) < kScaleEpsilon; - const bool unchanged_pixels = std::abs(st.scale_w - pixel_w) < kScaleEpsilon && - std::abs(st.scale_h - pixel_h) < kScaleEpsilon; - if (unchanged_point && unchanged_pixels) { - LOG_TRACE(Lib_Font, "SetScalePoint: handle={} unchanged point=({}, {})", - static_cast(fontHandle), w, h); - return ORBIS_OK; + + 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; } - st.scale_point_active = true; - st.scale_point_w = w; - st.scale_point_h = h; - st.scale_w = pixel_w; - st.scale_h = pixel_h; - if (st.ext_face_ready) - st.ext_scale_for_height = ComputeScaleExtForState(st, &st.ext_face, st.scale_h); - bool system_attached = false; - const std::string sys_log = ReportSystemFaceRequest(st, fontHandle, system_attached); - if (!sys_log.empty()) { - LOG_ERROR(Lib_Font, "{}", sys_log); + + u32 prev_font_lock = 0; + if (!AcquireFontLock(font, prev_font_lock)) { + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + return ORBIS_FONT_ERROR_INVALID_FONT_HANDLE; } - st.layout_cached = false; - LOG_INFO(Lib_Font, "scale point set requested"); - LOG_DEBUG(Lib_Font, - "scale point params:\n" - " handle={}\n" - " point_w={}\n" - " point_h={}\n" - " pixel_w={}\n" - " pixel_h={}\n" - " dpi_x={}\n" - " dpi_y={}\n" - " system_attached={}\n", - static_cast(fontHandle), w, h, st.scale_w, st.scale_h, st.dpi_x, - st.dpi_y, system_attached); + + 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; } @@ -3218,37 +5718,177 @@ 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 = 0; + } + 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 = 0; + } + 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(OrbisFontHandle fontHandle, float w, float h) { - auto rc = sceFontSetScalePixel(fontHandle, w, h); - LOG_INFO(Lib_Font, "render scale pixel setup requested"); - LOG_DEBUG(Lib_Font, - "render scale pixel setup params:\n" - " handle={}\n" - " w={}\n" - " h={}\n", - static_cast(fontHandle), w, 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 = 0; + } + 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(OrbisFontHandle fontHandle, float w, float h) { - auto rc = sceFontSetScalePoint(fontHandle, w, h); - LOG_INFO(Lib_Font, "render scale point setup requested"); - LOG_DEBUG(Lib_Font, - "render scale point setup params:\n" - " handle={}\n" - " w={}\n" - " h={}\n", - static_cast(fontHandle), w, 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 = 0; + } + 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; } @@ -3279,193 +5919,338 @@ s32 PS4_SYSV_ABI sceFontStringRefersTextCharacters() { s32 PS4_SYSV_ABI sceFontStyleFrameGetEffectSlant(OrbisFontStyleFrame* styleFrame, float* slantRatio) { - if (!styleFrame || !slantRatio) + if (!styleFrame || styleFrame->magic != kStyleFrameMagic) { return ORBIS_FONT_ERROR_INVALID_PARAMETER; - if (styleFrame->magic != kStyleFrameMagic) - InitializeStyleFrame(*styleFrame); - *slantRatio = (styleFrame->flags & kStyleFrameFlagSlant) ? styleFrame->slantRatio : 0.0f; - LOG_DEBUG(Lib_Font, "StyleFrameGetEffectSlant: frame={} slant={}", - static_cast(styleFrame), *slantRatio); + } + if ((styleFrame->flags1 & 2u) == 0) { + return ORBIS_FONT_ERROR_UNSET_PARAMETER; + } + if (!slantRatio) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } + *slantRatio = styleFrame->slantRatio; return ORBIS_OK; } s32 PS4_SYSV_ABI sceFontStyleFrameGetEffectWeight(OrbisFontStyleFrame* fontStyleFrame, float* weightXScale, float* weightYScale, uint32_t* mode) { - if (!fontStyleFrame) + if (!fontStyleFrame || fontStyleFrame->magic != kStyleFrameMagic) { return ORBIS_FONT_ERROR_INVALID_PARAMETER; - if (fontStyleFrame->magic != kStyleFrameMagic) - InitializeStyleFrame(*fontStyleFrame); - const bool has_weight = (fontStyleFrame->flags & kStyleFrameFlagWeight) != 0; - if (weightXScale) - *weightXScale = has_weight ? fontStyleFrame->weightXScale : 1.0f; - if (weightYScale) - *weightYScale = has_weight ? fontStyleFrame->weightYScale : 1.0f; - if (mode) + } + if ((fontStyleFrame->flags1 & 4u) == 0) { + return ORBIS_FONT_ERROR_UNSET_PARAMETER; + } + if (weightXScale) { + *weightXScale = fontStyleFrame->effectWeightX + 1.0f; + } + if (weightYScale) { + *weightYScale = fontStyleFrame->effectWeightY + 1.0f; + } + if (mode) { *mode = 0; - LOG_DEBUG(Lib_Font, "StyleFrameGetEffectWeight: frame={} weight=({}, {}) mode={}", - static_cast(fontStyleFrame), weightXScale ? *weightXScale : -1.0f, - weightYScale ? *weightYScale : -1.0f, mode ? *mode : 0u); + } + if (!weightXScale && !weightYScale && !mode) { + return ORBIS_FONT_ERROR_INVALID_PARAMETER; + } return ORBIS_OK; } s32 PS4_SYSV_ABI sceFontStyleFrameGetResolutionDpi(const OrbisFontStyleFrame* styleFrame, u32* h_dpi, u32* v_dpi) { - if (!ValidateStyleFramePtr(styleFrame) || (!h_dpi && !v_dpi)) + if (!styleFrame || styleFrame->magic != kStyleFrameMagic) { return ORBIS_FONT_ERROR_INVALID_PARAMETER; - if (h_dpi) - *h_dpi = styleFrame->dpiX > 0 ? static_cast(styleFrame->dpiX) : 0u; - if (v_dpi) - *v_dpi = styleFrame->dpiY > 0 ? static_cast(styleFrame->dpiY) : 0u; - LOG_DEBUG(Lib_Font, "StyleFrameGetResolutionDpi: frame={} -> ({}, {})", - static_cast(styleFrame), h_dpi ? *h_dpi : 0u, v_dpi ? *v_dpi : 0u); + } + 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(const OrbisFontStyleFrame* styleFrame, float* w, float* h) { - if (!ValidateStyleFramePtr(styleFrame) || (!w && !h)) + if (!styleFrame || styleFrame->magic != kStyleFrameMagic) { return ORBIS_FONT_ERROR_INVALID_PARAMETER; - const bool active = (styleFrame->flags & kStyleFrameFlagScale) != 0 && - styleFrame->scalingFlag == static_cast(StyleFrameScalingMode::Pixel); - if (w) - *w = active ? styleFrame->scaleWidth : 0.0f; - if (h) - *h = active ? styleFrame->scaleHeight : 0.0f; - LOG_DEBUG(Lib_Font, "StyleFrameGetScalePixel: frame={} -> w={}, h={}", - static_cast(styleFrame), w ? *w : 0.0f, h ? *h : 0.0f); + } + 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 * (static_cast(styleFrame->hDpi) / kPointsPerInch); + } + *w = out; + } + if (h) { + 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 (!ValidateStyleFramePtr(styleFrame) || (!w && !h)) + if (!styleFrame || styleFrame->magic != kStyleFrameMagic) { return ORBIS_FONT_ERROR_INVALID_PARAMETER; - const bool active = (styleFrame->flags & kStyleFrameFlagScale) != 0 && - styleFrame->scalingFlag == static_cast(StyleFrameScalingMode::Point); - if (w) - *w = active ? styleFrame->scaleWidth : 0.0f; - if (h) - *h = active ? styleFrame->scaleHeight : 0.0f; - LOG_DEBUG(Lib_Font, "StyleFrameGetScalePoint: frame={} -> w={}pt, h={}pt", - static_cast(styleFrame), w ? *w : 0.0f, h ? *h : 0.0f); + } + 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) + if (!styleFrame) { return ORBIS_FONT_ERROR_INVALID_PARAMETER; - InitializeStyleFrame(*styleFrame); - LOG_DEBUG(Lib_Font, "StyleFrameInit: frame={}", static_cast(styleFrame)); + } + 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 (!EnsureStyleFrameInitialized(styleFrame)) + if (!styleFrame || styleFrame->magic != kStyleFrameMagic) { return ORBIS_FONT_ERROR_INVALID_PARAMETER; - styleFrame->slantRatio = slantRatio; - styleFrame->flags |= kStyleFrameFlagSlant; - LOG_DEBUG(Lib_Font, "StyleFrameSetEffectSlant: frame={} slant={}", - static_cast(styleFrame), slantRatio); + } + + 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) { - (void)mode; - if (!EnsureStyleFrameInitialized(styleFrame)) + if (!styleFrame || styleFrame->magic != kStyleFrameMagic) { return ORBIS_FONT_ERROR_INVALID_PARAMETER; - styleFrame->weightXScale = weightXScale; - styleFrame->weightYScale = weightYScale; - styleFrame->flags |= kStyleFrameFlagWeight; - LOG_DEBUG(Lib_Font, "StyleFrameSetEffectWeight: frame={} weight=({}, {}) mode={}", - static_cast(styleFrame), weightXScale, weightYScale, mode); + } + 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 (!EnsureStyleFrameInitialized(styleFrame)) + if (!styleFrame || styleFrame->magic != kStyleFrameMagic) { return ORBIS_FONT_ERROR_INVALID_PARAMETER; - styleFrame->dpiX = static_cast(h_dpi); - styleFrame->dpiY = static_cast(v_dpi); - styleFrame->flags |= kStyleFrameFlagDpi; - LOG_DEBUG(Lib_Font, "StyleFrameSetResolutionDpi: frame={} -> ({}, {})", - static_cast(styleFrame), h_dpi, v_dpi); + } + 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 (!EnsureStyleFrameInitialized(styleFrame) || w <= 0.0f || h <= 0.0f) + if (!styleFrame || styleFrame->magic != kStyleFrameMagic) { return ORBIS_FONT_ERROR_INVALID_PARAMETER; - styleFrame->scaleWidth = w; - styleFrame->scaleHeight = h; - styleFrame->scalingFlag = static_cast(StyleFrameScalingMode::Pixel); - styleFrame->flags |= kStyleFrameFlagScale; - LOG_DEBUG(Lib_Font, "StyleFrameSetScalePixel: frame={} -> ({}, {})", - static_cast(styleFrame), w, h); + } + 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 (!EnsureStyleFrameInitialized(styleFrame) || w <= 0.0f || h <= 0.0f) + if (!styleFrame || styleFrame->magic != kStyleFrameMagic) { return ORBIS_FONT_ERROR_INVALID_PARAMETER; - styleFrame->scaleWidth = w; - styleFrame->scaleHeight = h; - styleFrame->scalingFlag = static_cast(StyleFrameScalingMode::Point); - styleFrame->flags |= kStyleFrameFlagScale; - LOG_DEBUG(Lib_Font, "StyleFrameSetScalePoint: frame={} -> ({}, {})pt", - static_cast(styleFrame), w, h); + } + styleFrame->scaleUnit = 1; + styleFrame->scalePixelW = w; + styleFrame->scalePixelH = h; + styleFrame->flags1 |= 1u; return ORBIS_OK; } s32 PS4_SYSV_ABI sceFontStyleFrameUnsetEffectSlant(OrbisFontStyleFrame* styleFrame) { - if (!EnsureStyleFrameInitialized(styleFrame)) + if (!styleFrame || styleFrame->magic != kStyleFrameMagic) { return ORBIS_FONT_ERROR_INVALID_PARAMETER; - styleFrame->flags &= ~kStyleFrameFlagSlant; - styleFrame->slantRatio = 0.0f; - LOG_DEBUG(Lib_Font, "StyleFrameUnsetEffectSlant: frame={}", - static_cast(styleFrame)); + } + styleFrame->flags1 &= 0xfdu; return ORBIS_OK; } s32 PS4_SYSV_ABI sceFontStyleFrameUnsetEffectWeight(OrbisFontStyleFrame* styleFrame) { - if (!EnsureStyleFrameInitialized(styleFrame)) + if (!styleFrame || styleFrame->magic != kStyleFrameMagic) { return ORBIS_FONT_ERROR_INVALID_PARAMETER; - styleFrame->flags &= ~kStyleFrameFlagWeight; - styleFrame->weightXScale = 1.0f; - styleFrame->weightYScale = 1.0f; - LOG_DEBUG(Lib_Font, "StyleFrameUnsetEffectWeight: frame={}", - static_cast(styleFrame)); + } + styleFrame->flags1 &= 0xfbu; return ORBIS_OK; } s32 PS4_SYSV_ABI sceFontStyleFrameUnsetScale(OrbisFontStyleFrame* styleFrame) { - if (!EnsureStyleFrameInitialized(styleFrame)) + if (!styleFrame || styleFrame->magic != kStyleFrameMagic) { return ORBIS_FONT_ERROR_INVALID_PARAMETER; - styleFrame->flags &= ~kStyleFrameFlagScale; - styleFrame->scalingFlag = static_cast(StyleFrameScalingMode::None); - styleFrame->scaleWidth = 0.0f; - styleFrame->scaleHeight = 0.0f; - LOG_DEBUG(Lib_Font, "StyleFrameUnsetScale: frame={}", static_cast(styleFrame)); + } + styleFrame->flags1 &= 0xfeu; return ORBIS_OK; } s32 PS4_SYSV_ABI sceFontSupportExternalFonts(OrbisFontLib library, u32 fontMax, u32 formats) { - LOG_INFO(Lib_Font, "external font support requested"); - LOG_DEBUG(Lib_Font, - "external font support params:\n" - " library={}\n" - " font_max={}\n" - " formats_mask=0x{:X}\n", - static_cast(library), fontMax, 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; + } + + using AllocFn = void*(PS4_SYSV_ABI*)(void* object, u32 size); + using FreeFn = void(PS4_SYSV_ABI*)(void* object, void* p); + 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])); + } + } + + header->lock_word = 0; + header->max_entries = fontMax; + header->base = list_head; + + 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; + } + + lib->external_fonts_ctx = ctx; + 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; - LogExternalFormatSupport(formats); + ls.owned_external_fonts_ctx = ctx; + ls.owned_external_fonts_ctx_size = ctx_size; + + ReleaseLibraryLock(lib, prev_lock_word); return ORBIS_OK; } @@ -3475,28 +6260,92 @@ s32 PS4_SYSV_ABI sceFontSupportGlyphs() { } s32 PS4_SYSV_ABI sceFontSupportSystemFonts(OrbisFontLib library) { - LOG_INFO(Lib_Font, "system font support requested"); - LOG_DEBUG(Lib_Font, - "system font support params:\n" - " library={}\n", - static_cast(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; } - auto& ls = GetLibState(library); - if (ls.support_system) { + using AllocFn = void*(PS4_SYSV_ABI*)(void* object, u32 size); + using FreeFn = void(PS4_SYSV_ABI*)(void* object, void* p); + 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; } - // Real implementation allocates a small system-font context; we just mark it available. - lib->sysfonts_ctx = lib->sysfonts_ctx ? lib->sysfonts_ctx : &g_sysfonts_ctx_stub; + 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; + + if (!Internal::g_mnt) { + Internal::g_mnt = Common::Singleton::Instance(); + } + if (Internal::g_mnt) { + const auto sysfont_base = GetSysFontBaseDir(); + if (!sysfont_base.empty() && !Internal::g_mnt->GetMount("/:dev_font:")) { + Internal::g_mnt->Mount(sysfont_base, "/:dev_font:", true); + } + } + + ReleaseLibraryLock(lib, prev_lock_word); return ORBIS_OK; } @@ -3532,11 +6381,38 @@ s32 PS4_SYSV_ABI sceFontTextSourceSetWritingForm() { s32 PS4_SYSV_ABI sceFontUnbindRenderer(OrbisFontHandle fontHandle) { LOG_INFO(Lib_Font, "called"); - LOG_DEBUG(Lib_Font, - "parameters:\n" - " fontHandle={}\n", - static_cast(fontHandle)); - return ORBIS_OK; + 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() { diff --git a/src/core/libraries/font/font.h b/src/core/libraries/font/font.h index 2525e7c2b..1684d4eea 100644 --- a/src/core/libraries/font/font.h +++ b/src/core/libraries/font/font.h @@ -31,27 +31,55 @@ struct OrbisFontOpenParams { }; struct OrbisFontGlyphMetrics { - float w; - float h; - float h_bearing_x; - float h_bearing_y; - float h_adv; - float v_bearing_x; - float v_bearing_y; - float v_adv; + 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 dx; - float dy; - float px; - float py; + float offsetX; + float offsetY; + float positionX; + float positionY; }; struct OrbisFontGlyphImageMetrics { - float bearing_x; - float bearing_y; - float dv; + float bearingX; + float bearingY; + float advance; float stride; u32 width; u32 height; @@ -106,20 +134,23 @@ struct OrbisFontResultStage { u32 u32_10; }; -struct OrbisFontResultSlot { - u8* maybe_addr; - u32 maybe_rowBytes; - u8 maybe_pixelSize; - u8 maybe_pixelFmt; +struct OrbisFontSurfaceImage { + u8* address; + u32 widthByte; + u8 pixelSizeByte; + u8 pixelFormat; + u16 pad16; }; struct OrbisFontRenderOutput { const OrbisFontResultStage* stage; - OrbisFontResultSlot slot; - u32 new_x; - u32 new_y; - u32 new_w; - u32 new_h; + OrbisFontSurfaceImage SurfaceImage; + struct { + u32 x; + u32 y; + u32 w; + u32 h; + } UpdateRect; OrbisFontGlyphImageMetrics ImageMetrics; }; @@ -163,16 +194,15 @@ struct OrbisFontMem { }; 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 OrbisFontTextCharacter* next; + struct OrbisFontTextCharacter* prev; + void* textOrder; + u32 characterCode; + u8 unknown_0x31; + u8 unknown_0x33; + u8 charType; + u8 bidiLevel; + u8 formatFlags; }; struct OrbisFontRenderSurface { @@ -191,19 +221,48 @@ struct OrbisFontRenderSurface { }; 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*/ float scaleWidth; // Width scaling factor - /*0x14*/ float scaleHeight; // Height scaling factor - /*0x18*/ float weightXScale; - /*0x1c*/ float weightYScale; - /*0x20*/ float slantRatio; - /*0x24*/ + /*0x00*/ u16 magic; + /*0x02*/ u8 flags1; + /*0x03*/ u8 flags2; + /*0x04*/ u32 hDpi; + /*0x08*/ u32 vDpi; + /*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; + /*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; @@ -278,8 +337,9 @@ 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(); -s32 PS4_SYSV_ABI sceFontGetEffectWeight(); +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(); @@ -294,8 +354,9 @@ s32 PS4_SYSV_ABI sceFontGetLibrary(OrbisFontHandle fontHandle, OrbisFontLib* pLi s32 PS4_SYSV_ABI sceFontGetPixelResolution(); s32 PS4_SYSV_ABI sceFontGetRenderCharGlyphMetrics(OrbisFontHandle fontHandle, u32 codepoint, OrbisFontGlyphMetrics* out_metrics); -s32 PS4_SYSV_ABI sceFontGetRenderEffectSlant(); -s32 PS4_SYSV_ABI sceFontGetRenderEffectWeight(); +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); @@ -393,7 +454,10 @@ s32 PS4_SYSV_ABI sceFontRenderCharGlyphImageHorizontal(OrbisFontHandle fontHandl OrbisFontRenderSurface* surf, float x, float y, OrbisFontGlyphMetrics* metrics, OrbisFontRenderOutput* result); -s32 PS4_SYSV_ABI sceFontRenderCharGlyphImageVertical(); +s32 PS4_SYSV_ABI sceFontRenderCharGlyphImageVertical(OrbisFontHandle fontHandle, u32 code, + OrbisFontRenderSurface* surf, float x, float y, + OrbisFontGlyphMetrics* metrics, + OrbisFontRenderOutput* result); s32 PS4_SYSV_ABI sceFontRendererGetOutlineBufferSize(); s32 PS4_SYSV_ABI sceFontRendererResetOutlineBuffer(); s32 PS4_SYSV_ABI sceFontRendererSetOutlineBufferPolicy(); @@ -404,16 +468,18 @@ void PS4_SYSV_ABI sceFontRenderSurfaceSetScissor(OrbisFontRenderSurface* renderS 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 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(); -s32 PS4_SYSV_ABI sceFontSetupRenderEffectWeight(); +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(); diff --git a/src/core/libraries/font/font_internal.cpp b/src/core/libraries/font/font_internal.cpp new file mode 100644 index 000000000..56d2c5e8f --- /dev/null +++ b/src/core/libraries/font/font_internal.cpp @@ -0,0 +1,1932 @@ +// SPDX-FileCopyrightText: Copyright 2024 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" + +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; + +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{}; + +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) { + return nullptr; + } + (void)FT_Select_Charmap(face, FT_ENCODING_UNICODE); + return face; +} + +void DestroyFreeTypeFace(FT_Face& face) { + if (!face) { + return; + } + FT_Done_Face(face); + face = nullptr; +} + +namespace { + +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 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 + +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)) { + struct SysFontRangeRecordLocal { + /*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(SysFontRangeRecordLocal) == 0x1C); + SysFontRangeRecordLocal 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) { + return g_font_state[h]; +} + +FontState* TryGetState(Libraries::Font::OrbisFontHandle h) { + if (!h) + return nullptr; + auto it = g_font_state.find(h); + if (it == g_font_state.end()) + return nullptr; + return &it->second; +} + +LibraryState& GetLibState(Libraries::Font::OrbisFontLib lib) { + return g_library_state[lib]; +} + +void RemoveLibState(Libraries::Font::OrbisFontLib lib) { + if (auto it = g_library_state.find(lib); it != g_library_state.end()) { + if (it->second.owned_device_cache) { + delete[] static_cast(it->second.owned_device_cache); + } + g_library_state.erase(it); + } +} + +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; +} + +// LLE: FUN_0100a690 +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; +} + +// LLE: FUN_0100e050 +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", "SSTArabic-Light.otf"}, + {0x180700C4, "FONTSET_SST_STD_EUROPEAN_AR", "SSTArabic-Roman.otf"}, + {0x180700C5, "FONTSET_SST_STD_EUROPEAN_AR_MEDIUM", "SSTArabic-Medium.otf"}, + {0x180700C7, "FONTSET_SST_STD_EUROPEAN_AR_BOLD", "SSTArabic-Bold.otf"}, + {0x18070444, "FONTSET_SST_STD_EUROPEAN_JP", "SSTVietnamese-Roman.otf"}, + {0x18070447, "FONTSET_SST_STD_EUROPEAN_JP_BOLD", "SSTVietnamese-Bold.otf"}, + {0x18070454, "FONTSET_SST_STD_EUROPEAN_JP", "SSTVietnamese-Roman.otf"}, + {0x18070457, "FONTSET_SST_STD_EUROPEAN_JP_BOLD", "SSTVietnamese-Bold.otf"}, + {0x180704C4, "FONTSET_SST_STD_EUROPEAN_JP_AR", "SSTArabic-Roman.otf"}, + {0x180704C7, "FONTSET_SST_STD_EUROPEAN_JP_AR_BOLD", "SSTArabic-Bold.otf"}, + {0x18071053, "FONTSET_SST_STD_THAI_LIGHT", "SSTThai-Light.otf"}, + {0x18071054, "FONTSET_SST_STD_THAI", "SSTThai-Roman.otf"}, + {0x18071055, "FONTSET_SST_STD_THAI_MEDIUM", "SSTThai-Medium.otf"}, + {0x18071057, "FONTSET_SST_STD_THAI_BOLD", "SSTThai-Bold.otf"}, + {0x18071454, "FONTSET_SST_STD_EUROPEAN_JP_TH", "SSTThai-Roman.otf"}, + {0x18071457, "FONTSET_SST_STD_EUROPEAN_JP_TH_BOLD", "SSTThai-Bold.otf"}, + {0x18072444, "FONTSET_SST_STD_EUROPEAN_JPUH", "SSTAribStdB24-Regular.ttf"}, + {0x18072447, "FONTSET_SST_STD_EUROPEAN_JPUH_BOLD", "SSTJpPro-Bold.otf"}, + {0x180724C4, "FONTSET_SST_STD_EUROPEAN_JPUH_AR", "SSTArabic-Roman.otf"}, + {0x180724C7, "FONTSET_SST_STD_EUROPEAN_JPUH_AR_BOLD", "SSTArabic-Bold.otf"}, + {0x18073454, "FONTSET_SST_STD_EUROPEAN_JPUH_TH", "SSTThai-Roman.otf"}, + {0x18073457, "FONTSET_SST_STD_EUROPEAN_JPUH_TH_BOLD", "SSTThai-Bold.otf"}, + {0x180734D4, "FONTSET_SST_STD_EUROPEAN_JPUH_TH_AR", "SSTThai-Roman.otf"}, + {0x180734D7, "FONTSET_SST_STD_EUROPEAN_JPUH_TH_AR_BOLD", "SSTThai-Bold.otf"}, + {0x18078044, "FONTSET_SST_STD_EUROPEAN_GB", "DFHEI5-SONY.ttf"}, + {0x180780C4, "FONTSET_SST_STD_EUROPEAN_GB_AR", "SSTArabic-Roman.otf"}, + {0x18079054, "FONTSET_SST_STD_EUROPEAN_GB_TH", "SSTThai-Roman.otf"}, + {0x1807A044, "FONTSET_SST_STD_EUROPEAN_GBUH", "e046323ms.ttf"}, + {0x1807A0C4, "FONTSET_SST_STD_EUROPEAN_GBUH_AR", "SSTArabic-Bold.otf"}, + {0x1807A444, "FONTSET_SST_STD_EUROPEAN_JPCJK", "SSTJpPro-Regular.otf"}, + {0x1807A4C4, "FONTSET_SST_STD_EUROPEAN_JPCJK_AR", "SSTArabic-Roman.otf"}, + {0x1807AC44, "FONTSET_SST_STD_EUROPEAN_GBCJK", "n023055ms.ttf"}, + {0x1807ACC4, "FONTSET_SST_STD_EUROPEAN_GBCJK_AR", "SSTArabic-Bold.otf"}, + {0x1807B054, "FONTSET_SST_STD_EUROPEAN_GBUH_TH", "SSTThai-Roman.otf"}, + {0x1807B0D4, "FONTSET_SST_STD_EUROPEAN_GBUH_TH_AR", "SSTThai-Bold.otf"}, + {0x1807B454, "FONTSET_SST_STD_EUROPEAN_JPCJK_TH", "SSTThai-Roman.otf"}, + {0x1807B4D4, "FONTSET_SST_STD_EUROPEAN_JPCJK_TH_AR", "SSTThai-Bold.otf"}, + {0x1807BC54, "FONTSET_SST_STD_EUROPEAN_GBCJK_TH", "SSTThai-Roman.otf"}, + {0x1807BCD4, "FONTSET_SST_STD_EUROPEAN_GBCJK_TH_AR", "SSTThai-Bold.otf"}, + {0x18080444, "FONTSET_SST_STD_JAPANESE_JP", "SSTAribStdB24-Regular.ttf"}, + {0x18080447, "FONTSET_SST_STD_JAPANESE_JP_BOLD", "SSTAribStdB24-Regular.ttf"}, + {0x18080454, "FONTSET_SST_STD_VIETNAMESE_JP", "SSTVietnamese-Roman.otf"}, + {0x18080457, "FONTSET_SST_STD_VIETNAMESE_JP_BOLD", "SSTVietnamese-Bold.otf"}, + {0x180804C4, "FONTSET_SST_STD_JAPANESE_JP_AR", "SSTAribStdB24-Regular.ttf"}, + {0x180804C7, "FONTSET_SST_STD_JAPANESE_JP_AR_BOLD", "SSTAribStdB24-Regular.ttf"}, + {0x18081454, "FONTSET_SST_STD_ASIAN_JP_TH", "SSTThai-Roman.otf"}, + {0x18081457, "FONTSET_SST_STD_ASIAN_JP_TH_BOLD", "SSTThai-Bold.otf"}, + {0x18082444, "FONTSET_SST_STD_JAPANESE_JPUH", "SSTAribStdB24-Regular.ttf"}, + {0x18082447, "FONTSET_SST_STD_JAPANESE_JPUH_BOLD", "SSTJpPro-Bold.otf"}, + {0x180824C4, "FONTSET_SST_STD_JAPANESE_JPUH_AR", "SSTAribStdB24-Regular.ttf"}, + {0x180824C7, "FONTSET_SST_STD_JAPANESE_JPUH_AR_BOLD", "SSTJpPro-Bold.otf"}, + {0x18083454, "FONTSET_SST_STD_ASIAN_JPUH_TH", "SSTThai-Roman.otf"}, + {0x18083457, "FONTSET_SST_STD_ASIAN_JPUH_TH_BOLD", "SSTThai-Bold.otf"}, + {0x180834D4, "FONTSET_SST_STD_ASIAN_JPUH_TH_AR", "SSTThai-Roman.otf"}, + {0x180834D7, "FONTSET_SST_STD_ASIAN_JPUH_TH_AR_BOLD", "SSTThai-Bold.otf"}, + {0x1808A444, "FONTSET_SST_STD_JAPANESE_JPCJK", "SSTAribStdB24-Regular.ttf"}, + {0x1808A4C4, "FONTSET_SST_STD_JAPANESE_JPCJK_AR", "SSTJpPro-Bold.otf"}, + {0x1808B454, "FONTSET_SST_STD_ASIAN_JPCJK_TH", "SSTThai-Roman.otf"}, + {0x1808B4D4, "FONTSET_SST_STD_ASIAN_JPCJK_TH_AR", "SSTThai-Bold.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", "SSTThai-Roman.otf"}, + {0x180CA044, "FONTSET_SST_STD_SCHINESE_GBUH", "e046323ms.ttf"}, + {0x180CA0C4, "FONTSET_SST_STD_SCHINESE_GBUH_AR", "e046323ms.ttf"}, + {0x180CAC44, "FONTSET_SST_STD_SCHINESE_GBCJK", "n023055ms.ttf"}, + {0x180CACC4, "FONTSET_SST_STD_SCHINESE_GBCJK_AR", "SSTAribStdB24-Regular.ttf"}, + {0x180CB054, "FONTSET_SST_STD_ASIAN_GBUH_TH", "SSTThai-Roman.otf"}, + {0x180CB0D4, "FONTSET_SST_STD_ASIAN_GBUH_TH_AR", "SSTThai-Bold.otf"}, + {0x180CBC54, "FONTSET_SST_STD_ASIAN_GBCJK_TH", "SSTThai-Roman.otf"}, + {0x180CBCD4, "FONTSET_SST_STD_ASIAN_GBCJK_TH_AR", "SSTThai-Bold.otf"}, + {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", "SSTTypewriter-Roman.otf"}, + {0x18370447, "FONTSET_SST_TYPEWRITER_EUROPEAN_JP_BOLD", "SSTTypewriter-Bd.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; +} + +static std::optional FindChildDirContainingFile( + const std::filesystem::path& base_dir, const std::string& filename) { + if (filename.empty()) { + return std::nullopt; + } + + std::error_code ec; + if (!std::filesystem::is_directory(base_dir, ec)) { + return std::nullopt; + } + + std::optional match; + for (const auto& entry : std::filesystem::directory_iterator(base_dir, ec)) { + if (ec) { + return std::nullopt; + } + if (!entry.is_directory(ec) || ec) { + continue; + } + const auto candidate = entry.path() / filename; + if (std::filesystem::is_regular_file(candidate, ec) && !ec) { + if (match) { + return std::nullopt; + } + match = entry.path(); + } + } + return match; +} + +std::filesystem::path GetSysFontBaseDir() { + std::filesystem::path base = Config::getSysFontPath(); + std::error_code ec; + if (base.empty()) { + LOG_ERROR(Lib_Font, "SystemFonts: SysFontPath not set"); + return {}; + } + if (std::filesystem::is_directory(base, ec)) { + if (DirectoryContainsAnyFontFiles(base)) { + return base; + } + + { + const auto preferred = base / "font"; + if (DirectoryContainsAnyFontFiles(preferred)) { + return preferred; + } + } + + const std::string fallback = Config::getSystemFontFallbackName(); + if (auto child = FindChildDirContainingFile(base, fallback)) { + return *child; + } + + std::optional sole_font_dir; + for (const auto& entry : std::filesystem::directory_iterator(base, ec)) { + if (ec) { + break; + } + if (!entry.is_directory(ec) || ec) { + continue; + } + if (DirectoryContainsAnyFontFiles(entry.path())) { + if (sole_font_dir) { + sole_font_dir.reset(); + break; + } + sole_font_dir = entry.path(); + } + } + if (sole_font_dir) { + return *sole_font_dir; + } + + LOG_ERROR( + Lib_Font, + "SystemFonts: SysFontPath '{}' contains no font files; set it to the directory that " + "contains the .otf/.ttf files (or ensure [SystemFonts].fallback is present in exactly " + "one child directory)", + base.string()); + return {}; + } + if (std::filesystem::is_regular_file(base, ec)) { + return base.parent_path(); + } + LOG_ERROR(Lib_Font, "SystemFonts: SysFontPath '{}' is not a valid directory or file", + base.string()); + return {}; +} + +std::string MacroToCamel(const char* macro_key) { + if (!macro_key) { + return {}; + } + std::string s(macro_key); + const std::string prefix = "FONTSET_"; + if (s.rfind(prefix, 0) != 0) { + return {}; + } + std::string out = "FontSet"; + size_t pos = prefix.size(); + while (pos < s.size()) { + size_t next = s.find('_', pos); + const size_t len = (next == std::string::npos) ? (s.size() - pos) : (next - pos); + std::string token = s.substr(pos, len); + for (auto& c : token) { + c = static_cast(std::tolower(static_cast(c))); + } + if (!token.empty()) { + token[0] = static_cast(std::toupper(static_cast(token[0]))); + } + out += token; + if (next == std::string::npos) { + break; + } + pos = next + 1; + } + return out; +} + +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 (auto override_path = Config::getSystemFontOverride(def->config_key)) { + if (!override_path->empty() && !override_path->is_absolute() && + !override_path->has_parent_path()) { + return base_dir / *override_path; + } + LOG_ERROR(Lib_Font, + "SystemFonts: override for '{}' must be a filename only (no path): '{}'", + def->config_key, override_path->string()); + } + const auto camel_key = MacroToCamel(def->config_key); + if (!camel_key.empty()) { + if (auto override_path2 = Config::getSystemFontOverride(camel_key)) { + if (!override_path2->empty() && !override_path2->is_absolute() && + !override_path2->has_parent_path()) { + return base_dir / *override_path2; + } + LOG_ERROR(Lib_Font, + "SystemFonts: override for '{}' must be a filename only (no path): '{}'", + camel_key, override_path2->string()); + } + std::string lower_camel = camel_key; + lower_camel[0] = + static_cast(std::tolower(static_cast(lower_camel[0]))); + if (auto override_path3 = Config::getSystemFontOverride(lower_camel)) { + if (!override_path3->empty() && !override_path3->is_absolute() && + !override_path3->has_parent_path()) { + return base_dir / *override_path3; + } + LOG_ERROR(Lib_Font, + "SystemFonts: override for '{}' must be a filename only (no path): '{}'", + lower_camel, override_path3->string()); + } + } + if (def->default_file && *def->default_file) { + return base_dir / def->default_file; + } + } + LOG_ERROR(Lib_Font, "SystemFonts: unknown font set type=0x{:08X}", font_set_type); + return {}; +} + +std::filesystem::path ResolveSystemFontPathFromConfigOnly(u32 font_set_type) { + const auto base_dir = GetSysFontBaseDir(); + if (base_dir.empty()) { + return {}; + } + + const std::string key_a = fmt::format("fontset_0x{:08X}", font_set_type); + const std::string key_b = fmt::format("fontSet_0x{:08X}", font_set_type); + + auto try_key = [&](const std::string& key) -> std::optional { + if (auto override_path = Config::getSystemFontOverride(key)) { + if (!override_path->empty() && override_path->is_absolute()) { + return *override_path; + } + if (!override_path->empty() && !override_path->has_parent_path()) { + return base_dir / *override_path; + } + LOG_ERROR( + Lib_Font, + "SystemFonts: override for '{}' must be a filename only or absolute path: '{}'", + key, override_path->string()); + } + return std::nullopt; + }; + + if (auto p = try_key(key_a)) { + return *p; + } + if (auto p = try_key(key_b)) { + return *p; + } + + const std::string fallback = Config::getSystemFontFallbackName(); + if (!fallback.empty()) { + return base_dir / fallback; + } + 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 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 = ResolveSystemFontPathFromConfigOnly(st.font_set_type); + if (primary_path.empty()) { + 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; + + const auto base_dir = GetSysFontBaseDir(); + if (!base_dir.empty()) { + auto resolve_override = + [&](const std::string& key) -> std::optional { + if (auto override_path = Config::getSystemFontOverride(key)) { + if (!override_path->empty() && override_path->is_absolute()) { + return *override_path; + } + if (!override_path->empty() && !override_path->has_parent_path()) { + return base_dir / *override_path; + } + } + return std::nullopt; + }; + + for (int i = 0; i < 8; ++i) { + const std::string key_a = + fmt::format("fontset_0x{:08X}_fallback{}", st.font_set_type, i); + const std::string key_b = + fmt::format("fontSet_0x{:08X}_fallback{}", st.font_set_type, i); + std::optional p = resolve_override(key_a); + if (!p) { + p = resolve_override(key_b); + } + if (!p || p->empty()) { + continue; + } + + std::vector fb_bytes; + if (!LoadFontFile(*p, fb_bytes)) { + continue; + } + FontState::SystemFallbackFace fb{}; + fb.font_id = static_cast(i + 1); + fb.scale_factor = 1.0f; + fb.shift_value = 0; + fb.path = *p; + fb.bytes = std::make_shared>(std::move(fb_bytes)); + fb.ft_face = + CreateFreeTypeFaceFromBytes(fb.bytes->data(), fb.bytes->size(), subfont_index); + fb.ready = (fb.ft_face != nullptr); + if (fb.ready) { + st.system_fallback_faces.push_back(std::move(fb)); + } else { + DestroyFreeTypeFace(fb.ft_face); + } + } + + const std::string global_fallback_name = Config::getSystemFontFallbackName(); + if (!global_fallback_name.empty()) { + const auto existing_name = primary_path.filename().string(); + if (existing_name != global_fallback_name) { + const std::filesystem::path fb_path = base_dir / global_fallback_name; + std::vector fb_bytes; + if (LoadFontFile(fb_path, fb_bytes)) { + FontState::SystemFallbackFace fb{}; + fb.font_id = 0xFFFFFFFFu; + fb.scale_factor = 1.0f; + fb.shift_value = 0; + fb.path = fb_path; + fb.bytes = std::make_shared>(std::move(fb_bytes)); + fb.ft_face = CreateFreeTypeFaceFromBytes(fb.bytes->data(), fb.bytes->size(), + subfont_index); + fb.ready = (fb.ft_face != nullptr); + if (fb.ready) { + st.system_fallback_faces.push_back(std::move(fb)); + } else { + DestroyFreeTypeFace(fb.ft_face); + } + } + } + } + } + + 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::getSysFontPath(); + return fmt::format("SystemFace: handle={} requested internal font but sysFontPath ('{}') " + "could not be loaded", + static_cast(handle), configured.string()); + } + return {}; +} + +} // 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..d1a12609c --- /dev/null +++ b/src/core/libraries/font/font_internal.h @@ -0,0 +1,760 @@ +// 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 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::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) { + if constexpr (std::is_same_v || std::is_same_v) { + return value ? fmt::format("\"{}\"", value) : "(null)"; + } + 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 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 { + 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(); +}; + +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; + 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"); + +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); +LibraryState& GetLibState(Libraries::Font::OrbisFontLib lib); +void RemoveLibState(Libraries::Font::OrbisFontLib lib); +FT_Face CreateFreeTypeFaceFromBytes(const unsigned char* data, std::size_t size, u32 subfont_index); +void DestroyFreeTypeFace(FT_Face& face); +void LogExternalFormatSupport(u32 formats_mask); +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::string MacroToCamel(const char* macro_key); +std::filesystem::path ResolveSystemFontPath(u32 font_set_type); +std::filesystem::path ResolveSystemFontPathFromConfigOnly(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); + +} // namespace Libraries::Font::Internal diff --git a/src/core/libraries/font/fontft.cpp b/src/core/libraries/font/fontft.cpp index 9bd43054b..db21f4207 100644 --- a/src/core/libraries/font/fontft.cpp +++ b/src/core/libraries/font/fontft.cpp @@ -1,43 +1,79 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include + #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 { namespace { -bool g_library_selected = false; -bool g_renderer_selected = false; -constexpr OrbisFontLibrarySelection kDefaultLibrarySelection{0xF2000000u, 0, nullptr, nullptr}; +static std::once_flag g_driver_table_once; +alignas(Libraries::Font::Internal::SysDriver) static Libraries::Font::Internal::SysDriver + g_driver_table{}; -static void* PS4_SYSV_ABI RendererCreateStub() { - LOG_ERROR(Lib_FontFt, "(STUBBED) renderer create called"); - return nullptr; +static const 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); } -static void PS4_SYSV_ABI RendererQueryStub() { - LOG_ERROR(Lib_FontFt, "(STUBBED) renderer query called"); +static std::once_flag g_renderer_table_once; +alignas(OrbisFontRendererSelection) static OrbisFontRendererSelection g_renderer_table{}; + +static const 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; } -static void PS4_SYSV_ABI RendererDestroyStub() { - LOG_ERROR(Lib_FontFt, "(STUBBED) renderer destroy called"); -} - -static OrbisFontRendererSelection MakeRendererSelection() { - OrbisFontRendererSelection sel{}; - sel.magic = 0xF2000000u; - sel.size = 0x168; - sel.create_fn = reinterpret_cast(&RendererCreateStub); - sel.query_fn = reinterpret_cast(&RendererQueryStub); - sel.destroy_fn = reinterpret_cast(&RendererDestroyStub); - return sel; -} - -static const OrbisFontRendererSelection kDefaultRendererSelection = MakeRendererSelection(); } // namespace s32 PS4_SYSV_ABI sceFontFtInitAliases() { @@ -136,31 +172,19 @@ s32 PS4_SYSV_ABI sceFontSelectGlyphsFt() { } const OrbisFontLibrarySelection* PS4_SYSV_ABI sceFontSelectLibraryFt(int value) { - if (!g_library_selected) { - g_library_selected = true; - LOG_INFO(Lib_FontFt, "library selection requested (FreeType shim)"); - } - LOG_DEBUG(Lib_FontFt, - "library selection params:\n" - " value={}\n", - value); + LOG_INFO(Lib_FontFt, "called"); + LOG_DEBUG(Lib_FontFt, "params:\nvalue: {}\n", value); if (value == 0) { - return &kDefaultLibrarySelection; + return GetDriverTable(); } return nullptr; } const OrbisFontRendererSelection* PS4_SYSV_ABI sceFontSelectRendererFt(int value) { - if (!g_renderer_selected) { - g_renderer_selected = true; - LOG_INFO(Lib_FontFt, "renderer selection requested (stb_truetype backend)"); - } - LOG_DEBUG(Lib_FontFt, - "renderer selection params:\n" - " value={}\n", - value); + LOG_INFO(Lib_FontFt, "called"); + LOG_DEBUG(Lib_FontFt, "params:\nvalue: {}\n", value); if (value == 0) { - return &kDefaultRendererSelection; + return GetRendererSelectionTable(); } return nullptr; } diff --git a/src/core/libraries/font/fontft.h b/src/core/libraries/font/fontft.h index a94e83102..f749e7724 100644 --- a/src/core/libraries/font/fontft.h +++ b/src/core/libraries/font/fontft.h @@ -15,15 +15,18 @@ struct OrbisFontLibrarySelection { u32 magic; u32 reserved; void* reserved_ptr1; - void* reserved_ptr2; + 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 query_fn; uintptr_t destroy_fn; + uintptr_t query_fn; }; s32 PS4_SYSV_ABI sceFontFtInitAliases(); diff --git a/src/core/libraries/font/fontft_internal.cpp b/src/core/libraries/font/fontft_internal.cpp new file mode 100644 index 000000000..6dc3d95bd --- /dev/null +++ b/src/core/libraries/font/fontft_internal.cpp @@ -0,0 +1,3672 @@ +// 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 "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; + +// LLE: FUN_01007e80 +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; +} + +// LLE: FUN_01000c30 +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); + } +} + +// LLE: FUN_01000d70 +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; +} + +// LLE: FUN_01000db0 +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; +} + +// LLE: FUN_01000ba0 +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; + } +} + +// LLE: FUN_01009ff0 +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); + } +} + +// LLE: FUN_0100bf00 +static std::uint64_t ResolveGposTagForCode(u32 codepoint) { + (void)codepoint; + return 0; +} + +#pragma pack(push, 1) +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; +}; +#pragma pack(pop) +static_assert(sizeof(SysFontRangeRecord) == 0x1C); +static_assert(offsetof(SysFontRangeRecord, shift_x) == 0x08); +static_assert(offsetof(SysFontRangeRecord, scale_mul) == 0x0C); + +// LLE: FUN_01000480 +static float SysFontScaleFactor(u32 font_id) { + (void)font_id; + return 1.0f; +} + +// LLE: FUN_010004d0 +static float SysFontShiftValueF32(u32 font_id) { + (void)font_id; + return 0.0f; +} + +// LLE: FUN_010004f0 +static u8 SysFontHasAltFlag(u32 font_id) { + (void)font_id; + return 0; +} + +// LLE: FUN_010006c0 +const std::uint8_t* FindSysFontRangeRecord(u32 font_id, u32 codepoint) { + (void)font_id; + (void)codepoint; + return nullptr; +} + +// LLE: FUN_010098b0 +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; +} + +// LLE: FUN_010008b0 +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; + } + + 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; + }; + + const u32 primary_id = selector->primary_font_id != 0xffffffffu ? selector->primary_font_id + : record_primary_id; + + auto has_glyph_in_ctx = [&](u32 font_id, u32 cp) -> bool { + if (!selector->library || selector->mode_low == 0) { + return false; + } + auto* lib = static_cast(selector->library); + auto* ctx = lib ? static_cast(lib->sysfonts_ctx) : nullptr; + if (!ctx) { + return false; + } + void* head = nullptr; + u32 lock_word = 0; + u8* entry_u8 = AcquireFontCtxEntry(ctx, font_id, selector->mode_low, &head, &lock_word); + auto* entry = entry_u8 ? reinterpret_cast(entry_u8) : nullptr; + auto* node = static_cast(head); + while (node) { + if (node->sub_font_index == selector->sub_font_index) { + break; + } + node = node->next; + } + FT_Face face = node ? static_cast(node->ft_face) : nullptr; + const bool ok = face && (FT_Get_Char_Index(face, static_cast(cp)) != 0); + if (entry) { + if (selector->mode_low == 3) { + entry->lock_mode3 = lock_word & 0x7fffffffu; + } else if (selector->mode_low == 2) { + entry->lock_mode2 = lock_word & 0x7fffffffu; + } else { + entry->lock_mode1 = lock_word & 0x7fffffffu; + } + } + return ok; + }; + + auto has_glyph = [&](u32 font_id, u32 cp) -> bool { + if (font_id == 0xffffffffu) { + return false; + } + if (has_glyph_in_ctx(font_id, cp)) { + return true; + } + for (const auto& c : selector->candidates) { + if (c.font_id != font_id || !c.face) { + continue; + } + return FT_Get_Char_Index(c.face, static_cast(cp)) != 0; + } + return false; + }; + + auto select_font = [&](u32 font_id) -> bool { + if (!has_glyph(font_id, code_u32)) { + return false; + } + if (out_font_id) { + *out_font_id = font_id; + } + return true; + }; + + if (selector->roman_font_id != 0xffffffffu && !is_cjk_like(code_u32)) { + if (select_font(selector->roman_font_id)) { + return code_u32; + } + } + if (select_font(primary_id)) { + return code_u32; + } + for (const auto& c : selector->candidates) { + if (select_font(c.font_id)) { + return code_u32; + } + } + + if (out_font_id) { + *out_font_id = primary_id; + } + (void)flags; + return 0; + } + + (void)flags; + if (out_font_id) { + *out_font_id = 0; + } + return code_u32; +} + +// LLE: FUN_010042e0 +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; + } + } +} + +// LLE: FUN_01006990 +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; + + struct F32x2 { + float lo = 0.0f; + float hi = 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; + (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; +} + +// LLE: FUN_01006fb0 +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) { + 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; +} + +// LLE: FUN_01007e40 +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) { + 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 { + +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"); + +inline RenderSurfaceSystemUse* GetSurfaceSystemUse(Libraries::Font::OrbisFontRenderSurface* surf) { + return surf ? reinterpret_cast(surf->reserved_q) : nullptr; +} +inline const RenderSurfaceSystemUse* GetSurfaceSystemUse( + const Libraries::Font::OrbisFontRenderSurface* surf) { + return surf ? reinterpret_cast(surf->reserved_q) : nullptr; +} + +// LLE: FUN_0100a690 +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; +} + +// LLE: FUN_0100a690 +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 = 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; +} + +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 + +// LLE: FUN_0100a690 +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) { + 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; + } + } + + 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); +} + +// LLE: FUN_0100e050 +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; +} + +// LLE: FUN_0100b6f0 +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; +} + +// LLE: FUN_0100b730 +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; +} + +// LLE: FUN_0100b770 +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; +} + +// LLE: FUN_0100b7e0 +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; +} + +// LLE: FUN_0100b860 +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; +} + +// LLE: FUN_0100b8b0 +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; +} + +// LLE: FUN_0100b8f0 +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; +} + +// LLE: FUN_0100b910 +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; +} + +// LLE: FUN_0100b9a0 +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 { + +using Libraries::Font::Internal::FontLibOpaque; +using Libraries::Font::Internal::FontObj; + +struct FtLibraryCtx { + void* alloc_ctx; + void** alloc_vtbl; + FT_Memory ft_memory; + FT_Library ft_lib; +}; + +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)); +} + +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"); + +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; +} + +struct FontObjSidecar { + const u8* font_data = nullptr; + u32 font_size = 0; + u32 sfnt_base = 0; + void* owned_data = nullptr; +}; + +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; + } + using AllocFn = void*(PS4_SYSV_ABI*)(void* object, u32 size); + 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; + } + using FreeFn = void(PS4_SYSV_ABI*)(void* object, void* p); + 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; + } + using ReallocFn = void*(PS4_SYSV_ABI*)(void* object, void* p, u32 size); + 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 + +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; + } + + using AllocFn = void*(PS4_SYSV_ABI*)(void* object, u32 size); + using FreeFn = void(PS4_SYSV_ABI*)(void* object, void* p); + 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; + using FreeFn = void(PS4_SYSV_ABI*)(void* object, void* p); + 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; + using AllocFn = void*(PS4_SYSV_ABI*)(void* object, u32 size); + using FreeFn = void(PS4_SYSV_ABI*)(void* object, void* p); + 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; + + auto assign_from_bytes = [&](std::vector&& bytes) -> s32 { + if (bytes.empty() || bytes.size() > std::numeric_limits::max()) { + return ORBIS_FONT_ERROR_FS_OPEN_FAILED; + } + + owned_data = alloc_fn(alloc_ctx, static_cast(bytes.size())); + if (!owned_data) { + return ORBIS_FONT_ERROR_ALLOCATION_FAILED; + } + std::memcpy(owned_data, bytes.data(), bytes.size()); + data = static_cast(owned_data); + size = static_cast(bytes.size()); + return ORBIS_OK; + }; + + 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; + } + + std::string open_path = path; + std::filesystem::path host_path_fs{}; + 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(); + } + } + + std::ifstream file(open_path, std::ios::binary | std::ios::ate); + if (!file && !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}; + const auto in_font_dir = sysfonts_dir / "font" / file_path; + const auto in_font2_dir = sysfonts_dir / "font2" / file_path; + std::error_code ec; + if (std::filesystem::exists(in_font_dir, ec)) { + open_path = in_font_dir.string(); + file = std::ifstream(open_path, std::ios::binary | std::ios::ate); + } else if (std::filesystem::exists(in_font2_dir, ec)) { + open_path = in_font2_dir.string(); + file = std::ifstream(open_path, std::ios::binary | std::ios::ate); + } else if (const auto alias = ResolveKnownSysFontAlias(sysfonts_dir, ps4_name)) { + open_path = alias->string(); + file = std::ifstream(open_path, std::ios::binary | std::ios::ate); + } + } + if (!file) { + return ORBIS_FONT_ERROR_FS_OPEN_FAILED; + } + const std::streamoff fsize = file.tellg(); + if (fsize <= 0 || static_cast(fsize) > std::numeric_limits::max()) { + return ORBIS_FONT_ERROR_FS_OPEN_FAILED; + } + file.seekg(0, std::ios::beg); + + std::vector bytes(static_cast(fsize)); + if (!file.read(reinterpret_cast(bytes.data()), + static_cast(bytes.size()))) { + return ORBIS_FONT_ERROR_FS_OPEN_FAILED; + } + const s32 store_rc = assign_from_bytes(std::move(bytes)); + if (store_rc != ORBIS_OK) { + return store_rc; + } + } 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; + const FT_Error ft_err = + FT_New_Memory_Face(ctx->ft_lib, reinterpret_cast(data), + static_cast(size), static_cast(subFontIndex), &face); + if (ft_err != 0 || !face) { + if (owned_data) { + free_fn(alloc_ctx, owned_data); + } + return ORBIS_FONT_ERROR_NO_SUPPORT_FORMAT; + } + + (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; + (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); + } + + using FreeFn = void(PS4_SYSV_ABI*)(void* object, void* p); + 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); + } + + struct LayoutOutIo { + u8 (*words)[16]; + struct F32Field { + u8* base; + std::size_t offset; + 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}, + }; + } + }; + + 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); + + struct LayoutAltOutIo { + u8 (*words)[16]; + struct F32Field { + u8* base; + std::size_t offset; + 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}, + }; + } + }; + + 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; +} + +// LLE: FUN_01007e80 (sys_driver +0xA8) +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; +} + +// LLE: FUN_0100a740 (sys_driver +0xB8) +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; +} + +// LLE: FUN_0100b750 (sys_driver +0xE0) +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; +} + +// LLE: FUN_0100d990 (sys_driver +0x108) +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..fbc1b85ca --- /dev/null +++ b/src/core/libraries/font/fontft_internal.h @@ -0,0 +1,603 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +#include "core/libraries/font/font.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"); + +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 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]); + +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 { + +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