diff --git a/src/core/libraries/font/font.cpp b/src/core/libraries/font/font.cpp index 59d9d74a0..f376975d5 100644 --- a/src/core/libraries/font/font.cpp +++ b/src/core/libraries/font/font.cpp @@ -1968,7 +1968,13 @@ s32 PS4_SYSV_ABI sceFontGetCharGlyphMetrics(OrbisFontHandle fontHandle, u32 code const s32 rc = Internal::GetCharGlyphMetrics(fontHandle, code, metrics, false); if (rc != ORBIS_OK) { - LOG_ERROR(Lib_Font, "FAILED"); + if (rc == ORBIS_FONT_ERROR_INVALID_FONT_HANDLE) { + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + } else if (rc == ORBIS_FONT_ERROR_INVALID_PARAMETER) { + LOG_ERROR(Lib_Font, "INVALID_PARAMETER"); + } else if (rc == ORBIS_FONT_ERROR_NOT_BOUND_RENDERER) { + LOG_ERROR(Lib_Font, "NOT_BOUND_RENDERER"); + } } return rc; } @@ -2250,7 +2256,13 @@ s32 PS4_SYSV_ABI sceFontGetRenderCharGlyphMetrics(OrbisFontHandle fontHandle, u3 const s32 rc = Internal::GetCharGlyphMetrics(fontHandle, codepoint, out_metrics, true); if (rc != ORBIS_OK) { - LOG_ERROR(Lib_Font, "FAILED"); + if (rc == ORBIS_FONT_ERROR_INVALID_FONT_HANDLE) { + LOG_ERROR(Lib_Font, "INVALID_HANDLE"); + } else if (rc == ORBIS_FONT_ERROR_INVALID_PARAMETER) { + LOG_ERROR(Lib_Font, "INVALID_PARAMETER"); + } else if (rc == ORBIS_FONT_ERROR_NOT_BOUND_RENDERER) { + LOG_ERROR(Lib_Font, "NOT_BOUND_RENDERER"); + } } return rc; } diff --git a/src/core/libraries/font/font.h b/src/core/libraries/font/font.h index 1684d4eea..fe0a53521 100644 --- a/src/core/libraries/font/font.h +++ b/src/core/libraries/font/font.h @@ -30,6 +30,9 @@ struct OrbisFontOpenParams { const void* reserved_ptr1; }; +// Fontlib APIs use Unicode scalar values (UTF-32 codepoints) in `u32` parameters such as `code` and +// `codepoint`. Codepoint-to-glyph resolution is performed via FreeType charmaps, with additional +// sysfont mapping and fallback behavior handled internally. struct OrbisFontGlyphMetrics { float width; float height; @@ -88,6 +91,8 @@ struct OrbisFontGlyphImageMetrics { struct OrbisFontGenerateGlyphParams { u16 id; u16 res0; + // Bitmask copied into `OrbisFontGlyphOpaque::flags`. Only bits `0x01` and `0x10` are accepted + // by the public API; other bits are rejected as invalid. u16 form_options; u8 glyph_form; u8 metrics_form; @@ -112,6 +117,7 @@ struct OrbisFontGlyphOutline { struct OrbisFontGlyphOpaque { u16 magic; + // Encoded flags copied from `OrbisFontGenerateGlyphParams::form_options`. u16 flags; u8 glyph_form; u8 metrics_form; @@ -222,10 +228,17 @@ struct OrbisFontRenderSurface { struct OrbisFontStyleFrame { /*0x00*/ u16 magic; + // `flags1` controls which fields are considered valid overrides: + // - bit0: scale override present (scaleUnit/scalePixelW/scalePixelH) + // - bit1: slant override present (slantRatio) + // - bit2: weight override present (effectWeightX/effectWeightY) /*0x02*/ u8 flags1; /*0x03*/ u8 flags2; /*0x04*/ u32 hDpi; /*0x08*/ u32 vDpi; + // `scaleUnit` selects how scale fields are interpreted: + // - 0: pixel scale (`scalePixelW/scalePixelH`) + // - 1: point scale (converted via DPI by style-state getters) /*0x0C*/ u32 scaleUnit; /*0x10*/ float baseScale; /*0x14*/ float scalePixelW; @@ -235,6 +248,9 @@ struct OrbisFontStyleFrame { /*0x24*/ float slantRatio; /*0x28*/ u32 reserved_0x28; /*0x2C*/ u32 layout_cache_state; + // Packed cache metadata: + // - bits [7:0] cache flags + // - bits [31:16] direction word /*0x30*/ u32 cache_flags_and_direction; /*0x34*/ u32 cache_lock_word; /*0x38*/ u8 layout_cache_bytes[0x20]; diff --git a/src/core/libraries/font/font_internal.h b/src/core/libraries/font/font_internal.h index d1a12609c..5d5b44f2e 100644 --- a/src/core/libraries/font/font_internal.h +++ b/src/core/libraries/font/font_internal.h @@ -200,6 +200,15 @@ struct GlyphEntry { }; struct FontState { + // `scale_*` fields are controlled by style-state setters and are interpreted according to the + // style frame `scaleUnit` (pixel vs point). Public APIs accept codepoints as UTF-32 scalar + // values; glyph lookup and rendering uses FreeType + deterministic fallback selection. + // + // Fallback behavior (high level): + // - A font handle may have an external face (opened from file/memory) and, optionally, a + // sysfont selection (font_set_type + primary sysfont + additional fallback sysfonts). + // - When a glyph is missing from the primary face, the implementation may consult the external + // face and then the configured system fallback faces to preserve observable behavior. float scale_w = 16.0f; float scale_h = 16.0f; float scale_point_w = 12.0f; diff --git a/src/core/libraries/font/fontft_internal.cpp b/src/core/libraries/font/fontft_internal.cpp index 57e3dc653..5aae88291 100644 --- a/src/core/libraries/font/fontft_internal.cpp +++ b/src/core/libraries/font/fontft_internal.cpp @@ -42,6 +42,52 @@ using Libraries::Font::OrbisFontRenderOutput; using Libraries::Font::OrbisFontRenderSurface; using Libraries::Font::OrbisFontStyleFrame; +namespace { +struct GetCharGlyphMetricsFailLogState { + u32 count = 0; + bool suppression_logged = false; +}; + +static thread_local GetCharGlyphMetricsFailLogState g_get_char_metrics_fail; + +static void LogGetCharGlyphMetricsFailOnce(std::string_view stage, s32 rc, FT_Error ft_err, + bool is_system, u32 code, FT_UInt glyph_index, + FT_Face face, float scale_w, float scale_h) { + constexpr u32 kMaxDetailedLogs = 16; + if (g_get_char_metrics_fail.count < kMaxDetailedLogs) { + u32 enc = 0; + u16 platform_id = 0; + u16 encoding_id = 0; + u32 num_glyphs = 0; + u32 num_charmaps = 0; + if (face) { + num_glyphs = static_cast(face->num_glyphs); + num_charmaps = static_cast(face->num_charmaps); + if (face->charmap) { + enc = static_cast(face->charmap->encoding); + platform_id = face->charmap->platform_id; + encoding_id = face->charmap->encoding_id; + } + } + + LOG_WARNING( + Lib_Font, + "GetCharGlyphMetricsFail: rc={} stage={} ft_err={} is_system={} code={} glyph_index={} " + "num_glyphs={} cmap(enc={},pid={},eid={}) num_charmaps={} scale_w={} scale_h={}", + rc, stage, static_cast(ft_err), is_system, code, glyph_index, num_glyphs, enc, + platform_id, encoding_id, num_charmaps, scale_w, scale_h); + + ++g_get_char_metrics_fail.count; + return; + } + + if (!g_get_char_metrics_fail.suppression_logged) { + LOG_WARNING(Lib_Font, "GetCharGlyphMetricsFail: further failures suppressed"); + g_get_char_metrics_fail.suppression_logged = true; + } +} +} // namespace + static void UpdateFtFontObjShiftCache(u8* font_obj) { if (!font_obj) { return; @@ -1293,6 +1339,9 @@ s32 GetCharGlyphMetrics(OrbisFontHandle fontHandle, u32 code, OrbisFontGlyphMetr } if (resolved_glyph_index == 0) { + LogGetCharGlyphMetricsFailOnce("no_glyph", ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH, 0, + font->open_info.fontset_record != nullptr, code, + resolved_glyph_index, resolved_face, scale_w, scale_h); if (use_cached_style) { font->cached_style.cache_lock_word = prev_cached_lock; }