// 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; } s32 RenderCodepointToSurface(FontState& st, Libraries::Font::OrbisFontHandle handle, FT_Face face, float pixel_w, float pixel_h, Libraries::Font::OrbisFontRenderSurface* surf, u32 code, float x, float y, Libraries::Font::OrbisFontGlyphMetrics* metrics, Libraries::Font::OrbisFontRenderOutput* result, s32 shift_x_units, s32 shift_y_units) { ClearRenderOutputs(metrics, result); if (!surf || !metrics || !result) { return ORBIS_FONT_ERROR_INVALID_PARAMETER; } if (!surf->buffer || surf->width <= 0 || surf->height <= 0 || surf->widthByte <= 0 || surf->pixelSizeByte <= 0) { return ORBIS_FONT_ERROR_NO_SUPPORT_SURFACE; } const int bpp = static_cast(surf->pixelSizeByte); if (bpp != 1 && bpp != 4) { return ORBIS_FONT_ERROR_NO_SUPPORT_SURFACE; } FT_Face primary_face = face; if (!primary_face) { float primary_scale_y = 0.0f; if (!ResolveFaceAndScale(st, handle, pixel_h, primary_face, primary_scale_y)) { return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH; } } auto resolve_scale_factor = [&](FT_Face candidate) -> float { if (!candidate) { return st.system_font_scale_factor; } if (candidate == st.ext_ft_face) { return st.system_font_scale_factor; } for (const auto& fb : st.system_fallback_faces) { if (fb.ready && fb.ft_face == candidate) { return fb.scale_factor; } } return st.system_font_scale_factor; }; FT_Face resolved_face = primary_face; FT_UInt resolved_glyph_index = resolved_face ? FT_Get_Char_Index(resolved_face, static_cast(code)) : 0; float resolved_scale_factor = resolve_scale_factor(resolved_face); if (resolved_glyph_index == 0) { if (st.ext_face_ready && st.ext_ft_face && resolved_face != st.ext_ft_face) { const FT_UInt gi = FT_Get_Char_Index(st.ext_ft_face, static_cast(code)); if (gi != 0) { resolved_face = st.ext_ft_face; resolved_glyph_index = gi; resolved_scale_factor = st.system_font_scale_factor; } } if (resolved_glyph_index == 0) { for (const auto& fb : st.system_fallback_faces) { if (!fb.ready || !fb.ft_face) { continue; } const FT_UInt gi = FT_Get_Char_Index(fb.ft_face, static_cast(code)); if (gi == 0) { continue; } resolved_face = fb.ft_face; resolved_glyph_index = gi; resolved_scale_factor = fb.scale_factor; break; } } } if (!resolved_face || resolved_glyph_index == 0) { return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH; } const auto set_size = [&](float w, float h) -> bool { const auto char_w = static_cast(static_cast(w * 64.0f)); const auto char_h = static_cast(static_cast(h * 64.0f)); return FT_Set_Char_Size(resolved_face, char_w, char_h, 72, 72) == 0; }; if (!set_size(pixel_w * resolved_scale_factor, pixel_h * resolved_scale_factor)) { return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH; } const float frac_x = x - std::floor(x); const float frac_y = y - std::floor(y); FT_Vector delta{}; delta.x = static_cast(static_cast(frac_x * 64.0f)); delta.y = static_cast(-static_cast(frac_y * 64.0f)); if ((shift_x_units != 0 || shift_y_units != 0) && resolved_face->size) { const long x_scale = static_cast(resolved_face->size->metrics.x_scale); const long y_scale = static_cast(resolved_face->size->metrics.y_scale); const auto round_fixed_mul = [](long fixed_16_16, s32 value) -> s32 { const long long prod = static_cast(fixed_16_16) * static_cast(value); const long long sign_adj = (static_cast(~static_cast(value)) >> 63) * -0x10000LL; const long long base = sign_adj + prod; long long tmp = base - 0x8000LL; if (tmp < 0) { tmp = base + 0x7FFFLL; } return static_cast(static_cast(tmp) >> 16); }; delta.x += static_cast(round_fixed_mul(x_scale, shift_x_units)); delta.y += static_cast(round_fixed_mul(y_scale, shift_y_units)); } FT_Set_Transform(resolved_face, nullptr, &delta); if (FT_Load_Glyph(resolved_face, resolved_glyph_index, kFtLoadFlagsRender) != 0) { FT_Set_Transform(resolved_face, nullptr, nullptr); return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH; } std::vector glyph_bitmap; int x0 = 0, y0 = 0; int glyph_w = 0; int glyph_h = 0; const FT_GlyphSlot slot = resolved_face->glyph; glyph_w = static_cast(slot->bitmap.width); glyph_h = static_cast(slot->bitmap.rows); x0 = static_cast(slot->bitmap_left); y0 = -static_cast(slot->bitmap_top); FT_Set_Transform(resolved_face, nullptr, nullptr); const float bearing_x = static_cast(slot->metrics.horiBearingX) / 64.0f; const float bearing_y = static_cast(slot->metrics.horiBearingY) / 64.0f; const float advance = static_cast(slot->metrics.horiAdvance) / 64.0f; const float width_px = static_cast(slot->metrics.width) / 64.0f; const float height_px = static_cast(slot->metrics.height) / 64.0f; metrics->width = width_px; metrics->height = height_px; metrics->Horizontal.bearingX = bearing_x; metrics->Horizontal.bearingY = bearing_y; metrics->Horizontal.advance = advance; metrics->Vertical.bearingX = 0.0f; metrics->Vertical.bearingY = 0.0f; metrics->Vertical.advance = 0.0f; glyph_bitmap.resize(static_cast(glyph_w) * static_cast(glyph_h)); if (glyph_w > 0 && glyph_h > 0) { const int pitch = static_cast(slot->bitmap.pitch); const unsigned char* src = reinterpret_cast(slot->bitmap.buffer); if (slot->bitmap.pixel_mode == FT_PIXEL_MODE_MONO) { for (int row = 0; row < glyph_h; ++row) { const unsigned char* src_row = src + static_cast(row) * pitch; unsigned char* dst_row = glyph_bitmap.data() + static_cast(row) * glyph_w; for (int col = 0; col < glyph_w; ++col) { const unsigned char byte = src_row[col >> 3]; const unsigned char bit = static_cast(0x80u >> (col & 7)); dst_row[col] = (byte & bit) ? 0xFFu : 0x00u; } } } else { for (int row = 0; row < glyph_h; ++row) { const unsigned char* src_row = src + static_cast(row) * pitch; unsigned char* dst_row = glyph_bitmap.data() + static_cast(row) * glyph_w; std::memcpy(dst_row, src_row, static_cast(glyph_w)); } } } const int dest_x = static_cast(std::floor(x)) + x0; const int dest_y = static_cast(std::floor(y)) + y0; const int surface_w = std::max(surf->width, 0); const int surface_h = std::max(surf->height, 0); const int clip_x0 = std::clamp(static_cast(surf->sc_x0), 0, surface_w); const int clip_y0 = std::clamp(static_cast(surf->sc_y0), 0, surface_h); const int clip_x1 = std::clamp(static_cast(surf->sc_x1), 0, surface_w); const int clip_y1 = std::clamp(static_cast(surf->sc_y1), 0, surface_h); int update_x0 = dest_x; int update_y0 = dest_y; int update_w = 0; int update_h = 0; if (glyph_w > 0 && glyph_h > 0 && clip_x1 > clip_x0 && clip_y1 > clip_y0) { auto* dst_base = static_cast(surf->buffer); const int start_row = std::max(dest_y, clip_y0); const int end_row = std::min(dest_y + glyph_h, clip_y1); const int start_col = std::max(dest_x, clip_x0); const int end_col = std::min(dest_x + glyph_w, clip_x1); update_x0 = start_col; update_y0 = start_row; update_w = std::max(0, end_col - start_col); update_h = std::max(0, end_row - start_row); for (int row = start_row; row < end_row; ++row) { const int src_y = row - dest_y; if (src_y < 0 || src_y >= glyph_h) continue; const std::uint8_t* src_row = glyph_bitmap.data() + static_cast(src_y) * glyph_w; std::uint8_t* dst_row = dst_base + static_cast(row) * static_cast(surf->widthByte); for (int col = start_col; col < end_col; ++col) { const int src_x = col - dest_x; if (src_x < 0 || src_x >= glyph_w) continue; const std::uint8_t cov = src_row[src_x]; std::uint8_t* dst = dst_row + static_cast(col) * bpp; if (bpp == 1) { dst[0] = cov; } else { dst[0] = cov; dst[1] = cov; dst[2] = cov; dst[3] = cov; } } } } result->stage = nullptr; result->SurfaceImage.address = static_cast(surf->buffer); result->SurfaceImage.widthByte = static_cast(surf->widthByte); result->SurfaceImage.pixelSizeByte = static_cast(surf->pixelSizeByte); result->SurfaceImage.pixelFormat = 0; result->SurfaceImage.pad16 = 0; result->UpdateRect.x = static_cast(std::max(update_x0, 0)); result->UpdateRect.y = static_cast(std::max(update_y0, 0)); result->UpdateRect.w = static_cast(std::max(update_w, 0)); result->UpdateRect.h = static_cast(std::max(update_h, 0)); result->SurfaceImage.address = static_cast(surf->buffer) + static_cast(result->UpdateRect.y) * static_cast(surf->widthByte) + static_cast(result->UpdateRect.x) * static_cast(bpp); const auto floor_int = [](float v) -> int { int i = static_cast(std::trunc(v)); if (static_cast(i) > v) { --i; } return i; }; const auto ceil_int = [](float v) -> int { int i = static_cast(std::trunc(v)); if (static_cast(i) < v) { ++i; } return i; }; const float left_f = x + metrics->Horizontal.bearingX; const float top_f = y + metrics->Horizontal.bearingY; const float right_f = left_f + metrics->width; const float bottom_f = top_f - metrics->height; const int left_i = floor_int(left_f); const int top_i = floor_int(top_f); const int right_i = ceil_int(right_f); const int bottom_i = ceil_int(bottom_f); const float adv_f = x + metrics->Horizontal.advance; const float adv_snapped = static_cast(floor_int(adv_f)) - x; result->ImageMetrics.bearingX = static_cast(left_i) - x; result->ImageMetrics.bearingY = static_cast(top_i) - y; result->ImageMetrics.advance = adv_snapped; int stride_i = right_i + 1; const float adjust = static_cast(right_i) - right_f; const int tmp_i = floor_int(adv_f + adjust); const int adv_trunc_i = static_cast(std::trunc(adv_f)); if (adv_trunc_i == 0) { stride_i = tmp_i; } if (stride_i < tmp_i) { stride_i = tmp_i; } result->ImageMetrics.stride = static_cast(stride_i) - x; result->ImageMetrics.width = static_cast(std::max(0, right_i - left_i)); result->ImageMetrics.height = static_cast(std::max(0, top_i - bottom_i)); return ORBIS_OK; } s32 RenderCodepointToSurfaceWithScale(FontState& st, Libraries::Font::OrbisFontHandle handle, FT_Face face, float scale_x, float scale_y, Libraries::Font::OrbisFontRenderSurface* surf, u32 code, float x, float y, Libraries::Font::OrbisFontGlyphMetrics* m, Libraries::Font::OrbisFontRenderOutput* result) { ClearRenderOutputs(m, result); if (!surf || !m || !result) { return ORBIS_FONT_ERROR_INVALID_PARAMETER; } if (scale_x <= kScaleEpsilon || scale_y <= kScaleEpsilon) { return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH; } if (!surf->buffer || surf->width <= 0 || surf->height <= 0 || surf->widthByte <= 0 || surf->pixelSizeByte <= 0) { return ORBIS_FONT_ERROR_NO_SUPPORT_SURFACE; } const int bpp = static_cast(surf->pixelSizeByte); if (bpp != 1 && bpp != 4) { return ORBIS_FONT_ERROR_NO_SUPPORT_SURFACE; } FT_Face primary_face = face; if (!ResolveFace(st, handle, primary_face)) { return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH; } FT_Face resolved_face = primary_face; FT_UInt resolved_glyph_index = resolved_face ? FT_Get_Char_Index(resolved_face, static_cast(code)) : 0; float resolved_scale_factor = st.system_font_scale_factor; if (resolved_glyph_index == 0) { for (const auto& fb : st.system_fallback_faces) { if (!fb.ready || !fb.ft_face) { continue; } const FT_UInt gi = FT_Get_Char_Index(fb.ft_face, static_cast(code)); if (gi == 0) { continue; } resolved_face = fb.ft_face; resolved_glyph_index = gi; resolved_scale_factor = fb.scale_factor; break; } } if (!resolved_face || resolved_glyph_index == 0) { return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH; } const int base_units = (st.ext_units_per_em > 0) ? st.ext_units_per_em : static_cast(resolved_face->units_per_EM); if (base_units <= 0) { return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH; } const float pixel_w = scale_x * static_cast(base_units) * resolved_scale_factor; const float pixel_h = scale_y * static_cast(base_units) * resolved_scale_factor; const auto set_size = [&](float w, float h) -> bool { const auto char_w = static_cast(static_cast(w * 64.0f)); const auto char_h = static_cast(static_cast(h * 64.0f)); return FT_Set_Char_Size(resolved_face, char_w, char_h, 72, 72) == 0; }; if (!set_size(pixel_w, pixel_h)) { return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH; } const float frac_x = x - std::floor(x); const float frac_y = y - std::floor(y); FT_Vector delta{}; delta.x = static_cast(static_cast(frac_x * 64.0f)); delta.y = static_cast(-static_cast(frac_y * 64.0f)); FT_Set_Transform(resolved_face, nullptr, &delta); if (FT_Load_Glyph(resolved_face, resolved_glyph_index, kFtLoadFlagsRender) != 0) { FT_Set_Transform(resolved_face, nullptr, nullptr); return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH; } std::vector glyph_bitmap; int x0 = 0, y0 = 0; int glyph_w = 0; int glyph_h = 0; const FT_GlyphSlot slot = resolved_face->glyph; glyph_w = static_cast(slot->bitmap.width); glyph_h = static_cast(slot->bitmap.rows); x0 = static_cast(slot->bitmap_left); y0 = -static_cast(slot->bitmap_top); FT_Set_Transform(resolved_face, nullptr, nullptr); const float bearing_x = static_cast(slot->metrics.horiBearingX) / 64.0f; const float bearing_y = static_cast(slot->metrics.horiBearingY) / 64.0f; const float advance = static_cast(slot->metrics.horiAdvance) / 64.0f; const float width_px = static_cast(slot->metrics.width) / 64.0f; const float height_px = static_cast(slot->metrics.height) / 64.0f; m->width = width_px; m->height = height_px; m->Horizontal.bearingX = bearing_x; m->Horizontal.bearingY = bearing_y; m->Horizontal.advance = advance; m->Vertical.bearingX = 0.0f; m->Vertical.bearingY = 0.0f; m->Vertical.advance = 0.0f; glyph_bitmap.resize(static_cast(glyph_w) * static_cast(glyph_h)); if (glyph_w > 0 && glyph_h > 0) { const int pitch = static_cast(slot->bitmap.pitch); const unsigned char* src = reinterpret_cast(slot->bitmap.buffer); if (slot->bitmap.pixel_mode == FT_PIXEL_MODE_MONO) { for (int row = 0; row < glyph_h; ++row) { const unsigned char* src_row = src + static_cast(row) * pitch; unsigned char* dst_row = glyph_bitmap.data() + static_cast(row) * glyph_w; for (int col = 0; col < glyph_w; ++col) { const unsigned char byte = src_row[col >> 3]; const unsigned char bit = static_cast(0x80u >> (col & 7)); dst_row[col] = (byte & bit) ? 0xFFu : 0x00u; } } } else { for (int row = 0; row < glyph_h; ++row) { const unsigned char* src_row = src + static_cast(row) * pitch; unsigned char* dst_row = glyph_bitmap.data() + static_cast(row) * glyph_w; std::memcpy(dst_row, src_row, static_cast(glyph_w)); } } } const int dest_x = static_cast(std::floor(x)) + x0; const int dest_y = static_cast(std::floor(y)) + y0; const int surface_w = std::max(surf->width, 0); const int surface_h = std::max(surf->height, 0); const int clip_x0 = std::clamp(static_cast(surf->sc_x0), 0, surface_w); const int clip_y0 = std::clamp(static_cast(surf->sc_y0), 0, surface_h); const int clip_x1 = std::clamp(static_cast(surf->sc_x1), 0, surface_w); const int clip_y1 = std::clamp(static_cast(surf->sc_y1), 0, surface_h); int update_x0 = dest_x; int update_y0 = dest_y; int update_w = 0; int update_h = 0; if (glyph_w > 0 && glyph_h > 0 && clip_x1 > clip_x0 && clip_y1 > clip_y0) { auto* dst_base = static_cast(surf->buffer); const int start_row = std::max(dest_y, clip_y0); const int end_row = std::min(dest_y + glyph_h, clip_y1); const int start_col = std::max(dest_x, clip_x0); const int end_col = std::min(dest_x + glyph_w, clip_x1); update_x0 = start_col; update_y0 = start_row; update_w = std::max(0, end_col - start_col); update_h = std::max(0, end_row - start_row); for (int row = start_row; row < end_row; ++row) { const int src_y = row - dest_y; if (src_y < 0 || src_y >= glyph_h) continue; const std::uint8_t* src_row = glyph_bitmap.data() + static_cast(src_y) * glyph_w; std::uint8_t* dst_row = dst_base + static_cast(row) * static_cast(surf->widthByte); for (int col = start_col; col < end_col; ++col) { const int src_x = col - dest_x; if (src_x < 0 || src_x >= glyph_w) continue; const std::uint8_t cov = src_row[src_x]; std::uint8_t* dst = dst_row + static_cast(col) * bpp; if (bpp == 1) { dst[0] = cov; } else { dst[0] = cov; dst[1] = cov; dst[2] = cov; dst[3] = cov; } } } } result->stage = nullptr; result->SurfaceImage.address = static_cast(surf->buffer); result->SurfaceImage.widthByte = static_cast(surf->widthByte); result->SurfaceImage.pixelSizeByte = static_cast(surf->pixelSizeByte); result->SurfaceImage.pixelFormat = 0; result->SurfaceImage.pad16 = 0; result->UpdateRect.x = static_cast(std::max(update_x0, 0)); result->UpdateRect.y = static_cast(std::max(update_y0, 0)); result->UpdateRect.w = static_cast(std::max(update_w, 0)); result->UpdateRect.h = static_cast(std::max(update_h, 0)); result->SurfaceImage.address = static_cast(surf->buffer) + static_cast(result->UpdateRect.y) * static_cast(surf->widthByte) + static_cast(result->UpdateRect.x) * static_cast(bpp); const auto floor_int = [](float v) -> int { int i = static_cast(std::trunc(v)); if (static_cast(i) > v) { --i; } return i; }; const auto ceil_int = [](float v) -> int { int i = static_cast(std::trunc(v)); if (static_cast(i) < v) { ++i; } return i; }; const float left_f = x + m->Horizontal.bearingX; const float top_f = y + m->Horizontal.bearingY; const float right_f = left_f + m->width; const float bottom_f = top_f - m->height; const int left_i = floor_int(left_f); const int top_i = floor_int(top_f); const int right_i = ceil_int(right_f); const int bottom_i = ceil_int(bottom_f); const float adv_f = x + m->Horizontal.advance; const float adv_snapped = static_cast(floor_int(adv_f)) - x; result->ImageMetrics.bearingX = static_cast(left_i) - x; result->ImageMetrics.bearingY = static_cast(top_i) - y; result->ImageMetrics.advance = adv_snapped; int stride_i = right_i + 1; const float adjust = static_cast(right_i) - right_f; const int tmp_i = floor_int(adv_f + adjust); const int adv_trunc_i = static_cast(std::trunc(adv_f)); if (adv_trunc_i == 0) { stride_i = tmp_i; } if (stride_i < tmp_i) { stride_i = tmp_i; } result->ImageMetrics.stride = static_cast(stride_i) - x; result->ImageMetrics.width = static_cast(std::max(0, right_i - left_i)); result->ImageMetrics.height = static_cast(std::max(0, top_i - bottom_i)); return ORBIS_OK; } const GlyphEntry* GetGlyphEntry(FontState& st, Libraries::Font::OrbisFontHandle handle, u32 code, FT_Face& face_out, float& scale_out) { face_out = nullptr; scale_out = 0.0f; if (st.ext_face_ready) { face_out = st.ext_ft_face; } if (!face_out) { bool system_attached = false; const std::string sys_log = ReportSystemFaceRequest(st, handle, system_attached); if (!sys_log.empty()) { LOG_ERROR(Lib_Font, "SYSTEM_FONT_FAILED"); LOG_DEBUG(Lib_Font, "{}", sys_log); } if (system_attached) { face_out = st.ext_ft_face; } } if (!face_out) { return nullptr; } float resolved_scale_factor = st.system_font_scale_factor; FT_Face resolved_face = face_out; FT_UInt resolved_glyph_index = FT_Get_Char_Index(resolved_face, static_cast(code)); if (resolved_glyph_index == 0) { for (const auto& fb : st.system_fallback_faces) { if (!fb.ready || !fb.ft_face) { continue; } const FT_UInt gi = FT_Get_Char_Index(fb.ft_face, static_cast(code)); if (gi == 0) { continue; } resolved_face = fb.ft_face; resolved_glyph_index = gi; resolved_scale_factor = fb.scale_factor; break; } } if (!resolved_face || resolved_glyph_index == 0) { return nullptr; } const float pixel_h_f = st.scale_h * resolved_scale_factor; const int pixel_h = std::max(1, static_cast(std::lround(pixel_h_f))); const std::uint64_t key = MakeGlyphKey(code, pixel_h); GlyphEntry* ge = nullptr; if (auto it = st.ext_cache.find(key); it != st.ext_cache.end()) { ge = &it->second; } if (!ge) { const auto set_size = [&](float h) -> bool { const auto char_h = static_cast(static_cast(h * 64.0f)); return FT_Set_Char_Size(resolved_face, 0, char_h, 72, 72) == 0; }; if (!set_size(pixel_h_f)) { return nullptr; } if (FT_Load_Glyph(resolved_face, resolved_glyph_index, kFtLoadFlagsRender) != 0) { return nullptr; } const FT_GlyphSlot slot = resolved_face->glyph; GlyphEntry entry{}; entry.w = static_cast(slot->bitmap.width); entry.h = static_cast(slot->bitmap.rows); entry.x0 = static_cast(slot->bitmap_left); entry.y0 = -static_cast(slot->bitmap_top); entry.x1 = entry.x0 + entry.w; entry.y1 = entry.y0 + entry.h; entry.advance = static_cast(slot->metrics.horiAdvance) / 64.0f; entry.bearingX = static_cast(slot->bitmap_left); ge = &st.ext_cache.emplace(key, std::move(entry)).first->second; } face_out = resolved_face; scale_out = ComputeScaleExtForState(st, resolved_face, pixel_h_f); return ge; } constexpr SystemFontDefinition kSystemFontDefinitions[] = { {0x18070043, "FONTSET_SST_STD_EUROPEAN_LIGHT", "SST-Light.otf"}, {0x18070044, "FONTSET_SST_STD_EUROPEAN", "SST-Roman.otf"}, {0x18070045, "FONTSET_SST_STD_EUROPEAN_MEDIUM", "SST-Medium.otf"}, {0x18070047, "FONTSET_SST_STD_EUROPEAN_BOLD", "SST-Bold.otf"}, {0x18070053, "FONTSET_SST_STD_VIETNAMESE_LIGHT", "SSTVietnamese-Light.otf"}, {0x18070054, "FONTSET_SST_STD_VIETNAMESE", "SSTVietnamese-Roman.otf"}, {0x18070055, "FONTSET_SST_STD_VIETNAMESE_MEDIUM", "SSTVietnamese-Medium.otf"}, {0x18070057, "FONTSET_SST_STD_VIETNAMESE_BOLD", "SSTVietnamese-Bold.otf"}, {0x180700C3, "FONTSET_SST_STD_EUROPEAN_AR_LIGHT", "SST-Light.otf"}, {0x180700C4, "FONTSET_SST_STD_EUROPEAN_AR", "SST-Roman.otf"}, {0x180700C5, "FONTSET_SST_STD_EUROPEAN_AR_MEDIUM", "SST-Medium.otf"}, {0x180700C7, "FONTSET_SST_STD_EUROPEAN_AR_BOLD", "SST-Bold.otf"}, {0x18070444, "FONTSET_SST_STD_EUROPEAN_JP", "SSTJpPro-Regular.otf"}, {0x18070447, "FONTSET_SST_STD_EUROPEAN_JP_BOLD", "SSTJpPro-Bold.otf"}, {0x18070454, "FONTSET_SST_STD_EUROPEAN_JP", "SSTJpPro-Regular.otf"}, {0x18070457, "FONTSET_SST_STD_EUROPEAN_JP_BOLD", "SSTJpPro-Bold.otf"}, {0x180704C4, "FONTSET_SST_STD_EUROPEAN_JP_AR", "SSTJpPro-Regular.otf"}, {0x180704C7, "FONTSET_SST_STD_EUROPEAN_JP_AR_BOLD", "SSTJpPro-Bold.otf"}, {0x18071053, "FONTSET_SST_STD_THAI_LIGHT", "SSTVietnamese-Light.otf"}, {0x18071054, "FONTSET_SST_STD_THAI", "SSTVietnamese-Roman.otf"}, {0x18071055, "FONTSET_SST_STD_THAI_MEDIUM", "SSTVietnamese-Medium.otf"}, {0x18071057, "FONTSET_SST_STD_THAI_BOLD", "SSTVietnamese-Bold.otf"}, {0x18071454, "FONTSET_SST_STD_EUROPEAN_JP_TH", "SSTJpPro-Regular.otf"}, {0x18071457, "FONTSET_SST_STD_EUROPEAN_JP_TH_BOLD", "SSTJpPro-Bold.otf"}, {0x18072444, "FONTSET_SST_STD_EUROPEAN_JPUH", "SSTJpPro-Regular.otf"}, {0x18072447, "FONTSET_SST_STD_EUROPEAN_JPUH_BOLD", "SSTJpPro-Bold.otf"}, {0x180724C4, "FONTSET_SST_STD_EUROPEAN_JPUH_AR", "SSTJpPro-Regular.otf"}, {0x180724C7, "FONTSET_SST_STD_EUROPEAN_JPUH_AR_BOLD", "SSTJpPro-Bold.otf"}, {0x18073454, "FONTSET_SST_STD_EUROPEAN_JPUH_TH", "SSTJpPro-Regular.otf"}, {0x18073457, "FONTSET_SST_STD_EUROPEAN_JPUH_TH_BOLD", "SSTJpPro-Bold.otf"}, {0x180734D4, "FONTSET_SST_STD_EUROPEAN_JPUH_TH_AR", "SSTJpPro-Regular.otf"}, {0x180734D7, "FONTSET_SST_STD_EUROPEAN_JPUH_TH_AR_BOLD", "SSTJpPro-Bold.otf"}, {0x18078044, "FONTSET_SST_STD_EUROPEAN_GB", "DFHEI5-SONY.ttf"}, {0x180780C4, "FONTSET_SST_STD_EUROPEAN_GB_AR", "DFHEI5-SONY.ttf"}, {0x18079054, "FONTSET_SST_STD_EUROPEAN_GB_TH", "DFHEI5-SONY.ttf"}, {0x1807A044, "FONTSET_SST_STD_EUROPEAN_GBUH", "DFHEI5-SONY.ttf"}, {0x1807A0C4, "FONTSET_SST_STD_EUROPEAN_GBUH_AR", "DFHEI5-SONY.ttf"}, {0x1807A444, "FONTSET_SST_STD_EUROPEAN_JPCJK", "SSTJpPro-Regular.otf"}, {0x1807A4C4, "FONTSET_SST_STD_EUROPEAN_JPCJK_AR", "SSTJpPro-Regular.otf"}, {0x1807AC44, "FONTSET_SST_STD_EUROPEAN_GBCJK", "SCEPS4Yoongd-Medium.otf"}, {0x1807ACC4, "FONTSET_SST_STD_EUROPEAN_GBCJK_AR", "DFHEI5-SONY.ttf"}, {0x1807B054, "FONTSET_SST_STD_EUROPEAN_GBUH_TH", "DFHEI5-SONY.ttf"}, {0x1807B0D4, "FONTSET_SST_STD_EUROPEAN_GBUH_TH_AR", "DFHEI5-SONY.ttf"}, {0x1807B454, "FONTSET_SST_STD_EUROPEAN_JPCJK_TH", "SSTJpPro-Regular.otf"}, {0x1807B4D4, "FONTSET_SST_STD_EUROPEAN_JPCJK_TH_AR", "SSTJpPro-Regular.otf"}, {0x1807BC54, "FONTSET_SST_STD_EUROPEAN_GBCJK_TH", "DFHEI5-SONY.ttf"}, {0x1807BCD4, "FONTSET_SST_STD_EUROPEAN_GBCJK_TH_AR", "DFHEI5-SONY.ttf"}, {0x18080444, "FONTSET_SST_STD_JAPANESE_JP", "SSTJpPro-Regular.otf"}, {0x18080447, "FONTSET_SST_STD_JAPANESE_JP_BOLD", "SSTJpPro-Bold.otf"}, {0x18080454, "FONTSET_SST_STD_VIETNAMESE_JP", "SSTJpPro-Regular.otf"}, {0x18080457, "FONTSET_SST_STD_VIETNAMESE_JP_BOLD", "SSTJpPro-Bold.otf"}, {0x180804C4, "FONTSET_SST_STD_JAPANESE_JP_AR", "SSTJpPro-Regular.otf"}, {0x180804C7, "FONTSET_SST_STD_JAPANESE_JP_AR_BOLD", "SSTJpPro-Bold.otf"}, {0x18081454, "FONTSET_SST_STD_ASIAN_JP_TH", "SSTJpPro-Regular.otf"}, {0x18081457, "FONTSET_SST_STD_ASIAN_JP_TH_BOLD", "SSTJpPro-Bold.otf"}, {0x18082444, "FONTSET_SST_STD_JAPANESE_JPUH", "SSTJpPro-Regular.otf"}, {0x18082447, "FONTSET_SST_STD_JAPANESE_JPUH_BOLD", "SSTJpPro-Bold.otf"}, {0x180824C4, "FONTSET_SST_STD_JAPANESE_JPUH_AR", "SSTJpPro-Regular.otf"}, {0x180824C7, "FONTSET_SST_STD_JAPANESE_JPUH_AR_BOLD", "SSTJpPro-Bold.otf"}, {0x18083454, "FONTSET_SST_STD_ASIAN_JPUH_TH", "SSTJpPro-Regular.otf"}, {0x18083457, "FONTSET_SST_STD_ASIAN_JPUH_TH_BOLD", "SSTJpPro-Bold.otf"}, {0x180834D4, "FONTSET_SST_STD_ASIAN_JPUH_TH_AR", "SSTJpPro-Regular.otf"}, {0x180834D7, "FONTSET_SST_STD_ASIAN_JPUH_TH_AR_BOLD", "SSTJpPro-Bold.otf"}, {0x1808A444, "FONTSET_SST_STD_JAPANESE_JPCJK", "SSTJpPro-Regular.otf"}, {0x1808A4C4, "FONTSET_SST_STD_JAPANESE_JPCJK_AR", "SSTJpPro-Regular.otf"}, {0x1808B454, "FONTSET_SST_STD_ASIAN_JPCJK_TH", "SSTJpPro-Regular.otf"}, {0x1808B4D4, "FONTSET_SST_STD_ASIAN_JPCJK_TH_AR", "SSTJpPro-Regular.otf"}, {0x180C8044, "FONTSET_SST_STD_SCHINESE_GB", "DFHEI5-SONY.ttf"}, {0x180C80C4, "FONTSET_SST_STD_SCHINESE_GB_AR", "DFHEI5-SONY.ttf"}, {0x180C9054, "FONTSET_SST_STD_ASIAN_GB_TH", "DFHEI5-SONY.ttf"}, {0x180CA044, "FONTSET_SST_STD_SCHINESE_GBUH", "DFHEI5-SONY.ttf"}, {0x180CA0C4, "FONTSET_SST_STD_SCHINESE_GBUH_AR", "DFHEI5-SONY.ttf"}, {0x180CAC44, "FONTSET_SST_STD_SCHINESE_GBCJK", "DFHEI5-SONY.ttf"}, {0x180CACC4, "FONTSET_SST_STD_SCHINESE_GBCJK_AR", "DFHEI5-SONY.ttf"}, {0x180CB054, "FONTSET_SST_STD_ASIAN_GBUH_TH", "DFHEI5-SONY.ttf"}, {0x180CB0D4, "FONTSET_SST_STD_ASIAN_GBUH_TH_AR", "DFHEI5-SONY.ttf"}, {0x180CBC54, "FONTSET_SST_STD_ASIAN_GBCJK_TH", "DFHEI5-SONY.ttf"}, {0x180CBCD4, "FONTSET_SST_STD_ASIAN_GBCJK_TH_AR", "DFHEI5-SONY.ttf"}, {0x18170043, "FONTSET_SST_STD_EUROPEAN_LIGHT_ITALIC", "SST-LightItalic.otf"}, {0x18170044, "FONTSET_SST_STD_EUROPEAN_ITALIC", "SST-Italic.otf"}, {0x18170045, "FONTSET_SST_STD_EUROPEAN_MEDIUM_ITALIC", "SST-MediumItalic.otf"}, {0x18170047, "FONTSET_SST_STD_EUROPEAN_BOLD_ITALIC", "SST-BoldItalic.otf"}, {0x18170444, "FONTSET_SST_STD_EUROPEAN_JP_ITALIC", "SSTJpPro-Regular.otf"}, {0x18170447, "FONTSET_SST_STD_EUROPEAN_JP_BOLD_ITALIC", "SSTJpPro-Bold.otf"}, {0x18370044, "FONTSET_SST_TYPEWRITER_EUROPEAN", "SSTTypewriter-Roman.otf"}, {0x18370047, "FONTSET_SST_TYPEWRITER_EUROPEAN_BOLD", "SSTTypewriter-Bd.otf"}, {0x18370444, "FONTSET_SST_TYPEWRITER_EUROPEAN_JP", "SSTJpPro-Regular.otf"}, {0x18370447, "FONTSET_SST_TYPEWRITER_EUROPEAN_JP_BOLD", "SSTJpPro-Bold.otf"}, }; std::mutex g_fontset_cache_mutex; std::unordered_map g_fontset_cache; const SystemFontDefinition* FindSystemFontDefinition(u32 font_set_type) { for (const auto& def : kSystemFontDefinitions) { if (def.font_set_type == font_set_type) { return &def; } } return nullptr; } static bool DirectoryContainsAnyFontFiles(const std::filesystem::path& dir) { std::error_code ec; if (!std::filesystem::is_directory(dir, ec)) { return false; } for (const auto& entry : std::filesystem::directory_iterator(dir, ec)) { if (ec) { return false; } if (!entry.is_regular_file(ec) || ec) { continue; } const auto ext = entry.path().extension().string(); std::string lower; lower.reserve(ext.size()); for (const char c : ext) { lower.push_back(static_cast(std::tolower(static_cast(c)))); } if (lower == ".otf" || lower == ".ttf" || lower == ".ttc" || lower == ".otc") { return true; } } return false; } 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 {}; } static std::filesystem::path ResolveSystemFontPathCandidate(const std::filesystem::path& base_dir, const std::filesystem::path& filename) { if (base_dir.empty() || filename.empty()) { return {}; } std::error_code ec; const auto is_file = [&](const std::filesystem::path& p) -> bool { return std::filesystem::is_regular_file(p, ec) && !ec; }; const auto direct = base_dir / filename; if (is_file(direct)) { return direct; } const auto base_name = base_dir.filename().string(); if (base_name != "font" && base_name != "font2") { const auto in_font = base_dir / "font" / filename; if (is_file(in_font)) { return in_font; } const auto in_font2 = base_dir / "font2" / filename; if (is_file(in_font2)) { return in_font2; } } if (base_name == "font" || base_name == "font2") { const auto container = base_dir.parent_path(); const auto sibling = container / ((base_name == "font") ? "font2" : "font") / filename; if (is_file(sibling)) { return sibling; } } return direct; } std::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 ResolveSystemFontPathCandidate(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 ResolveSystemFontPathCandidate(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 ResolveSystemFontPathCandidate(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 ResolveSystemFontPathCandidate(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 cand_font = parent / "font" / file_name; if (std::filesystem::is_regular_file(cand_font, ec) && !ec) { if (try_load(cand_font)) { return true; } } const auto cand_font2 = parent / "font2" / file_name; if (std::filesystem::is_regular_file(cand_font2, ec) && !ec) { if (try_load(cand_font2)) { return true; } } } if (!file_name.empty() && (parent_name == "font" || parent_name == "font2")) { const auto container = parent.parent_path(); const auto sibling = container / ((parent_name == "font") ? "font2" : "font") / file_name; if (std::filesystem::is_regular_file(sibling, ec) && !ec) { return try_load(sibling); } } return false; } bool AttachSystemFont(FontState& st, Libraries::Font::OrbisFontHandle handle) { if (st.ext_face_ready) { return true; } st.system_fallback_faces.clear(); st.system_font_id = 0; st.system_font_scale_factor = 1.0f; st.system_font_shift_value = 0; const auto* native = reinterpret_cast(handle); const u32 subfont_index = native ? native->open_info.sub_font_index : 0u; auto* lib = static_cast(st.library); if (!lib || lib->magic != 0x0F01 || st.font_set_type == 0) { return false; } std::filesystem::path primary_path = 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