From fe79da22ffd09da9d8abc5bcadd17afbe3d454d0 Mon Sep 17 00:00:00 2001 From: Yunus Shaikh <75296019+EagleBearerYS@users.noreply.github.com> Date: Sun, 25 Oct 2026 07:27:33 +0000 Subject: [PATCH 1/4] refactor(libSceFont): split RenderCodeWithStyle and ApplyStyleFrame out of the char render path RenderCharGlyphImageCore keeps its handle and bound-renderer checks and hands the rest of the render, from the glyph lookup to the output fill, to RenderCodeWithStyle, which takes the style as an argument. The surface style-frame rule moves into ApplyStyleFrame, with SurfaceStyleFrame finding a surface's valid frame, and ResolveSurfaceStyleState now calls the two. The char render calls pass the same style as before and behave as before. --- .../prx/libSceFont/include/FontInternal.hpp | 3 + core/libs/prx/libSceFont/src/Layout.cpp | 64 ++++++++++++------- 2 files changed, 43 insertions(+), 24 deletions(-) diff --git a/core/libs/prx/libSceFont/include/FontInternal.hpp b/core/libs/prx/libSceFont/include/FontInternal.hpp index 54c173835e..90ec4973f0 100644 --- a/core/libs/prx/libSceFont/include/FontInternal.hpp +++ b/core/libs/prx/libSceFont/include/FontInternal.hpp @@ -121,6 +121,9 @@ int StyleStateGetSlantRatio(const StyleStateBlock* style, float* slantRatio); int StyleStateSetWeightScale(StyleStateBlock* style, float weightXScale, float weightYScale); int StyleStateGetWeightScale(const StyleStateBlock* style, float* weightXScale, float* weightYScale, std::uint32_t* mode); bool ValidStyleFrame(const FontStyleFrame* frame); +const FontStyleFrame* SurfaceStyleFrame(const FontRenderSurface* surface); +const StyleStateBlock* ApplyStyleFrame(const StyleStateBlock* base, const FontStyleFrame* frame, StyleStateBlock& temporary); +int RenderCodeWithStyle(FontHandle handle, std::uint32_t code, const StyleStateBlock* style, FontRenderSurface* surface, float x, float y, FontGlyphMetrics* metrics, FontRenderOutput* result); std::uint8_t CachedStyleCacheFlags(const CachedStyle& style); void CachedStyleSetCacheFlags(CachedStyle& style, std::uint8_t flags); diff --git a/core/libs/prx/libSceFont/src/Layout.cpp b/core/libs/prx/libSceFont/src/Layout.cpp index 8bce6e00c2..f77e392e4c 100644 --- a/core/libs/prx/libSceFont/src/Layout.cpp +++ b/core/libs/prx/libSceFont/src/Layout.cpp @@ -68,28 +68,7 @@ void ClearVerticalLayout(std::uint8_t* outWords) { } const StyleStateBlock* ResolveSurfaceStyleState(const StyleStateBlock* cachedState, const FontRenderSurface* surface, StyleStateBlock& temporary) { - const auto* systemUse = reinterpret_cast(surface->reserved_q); - if ((surface->styleFlag & 0x1) == 0 || !ValidStyleFrame(systemUse->styleframe)) return cachedState; - const FontStyleFrame* frame = systemUse->styleframe; - temporary = *cachedState; - if ((frame->flags1 & STYLE_FRAME_FLAG_SCALE) != 0) { - temporary.scale_unit = frame->scaleUnit; - temporary.scale_w = frame->scalePixelW; - temporary.scale_h = frame->scalePixelH; - temporary.dpi_x = frame->hDpi ? frame->hDpi : 0x48; - temporary.dpi_y = frame->vDpi ? frame->vDpi : 0x48; - } else { - const std::uint32_t wantX = frame->hDpi == 0 ? temporary.dpi_x : frame->hDpi; - const std::uint32_t wantY = frame->vDpi == 0 ? temporary.dpi_y : frame->vDpi; - temporary.dpi_x = wantX != 0 ? 0x48 : 0; - temporary.dpi_y = wantY != 0 ? 0x48 : 0; - } - if ((frame->flags1 & STYLE_FRAME_FLAG_SLANT) != 0) temporary.slant_ratio = frame->slantRatio; - if ((frame->flags1 & STYLE_FRAME_FLAG_WEIGHT) != 0) { - temporary.effect_weight_x = frame->effectWeightX; - temporary.effect_weight_y = frame->effectWeightY; - } - return &temporary; + return ApplyStyleFrame(cachedState, SurfaceStyleFrame(surface), temporary); } void FillImageMetrics(FontRenderOutput* result, const FontGlyphMetrics* metrics, float x, float y) { @@ -440,6 +419,35 @@ int Font::GetCharGlyphMetrics(FontHandle handle, std::uint32_t code, FontGlyphMe return finish(SCE_FONT_OK); } +const FontStyleFrame* Font::SurfaceStyleFrame(const FontRenderSurface* surface) { + const auto* systemUse = reinterpret_cast(surface->reserved_q); + if ((surface->styleFlag & 0x1) == 0 || !ValidStyleFrame(systemUse->styleframe)) return nullptr; + return systemUse->styleframe; +} + +const StyleStateBlock* Font::ApplyStyleFrame(const StyleStateBlock* base, const FontStyleFrame* frame, StyleStateBlock& temporary) { + if (!frame) return base; + temporary = *base; + if ((frame->flags1 & STYLE_FRAME_FLAG_SCALE) != 0) { + temporary.scale_unit = frame->scaleUnit; + temporary.scale_w = frame->scalePixelW; + temporary.scale_h = frame->scalePixelH; + temporary.dpi_x = frame->hDpi ? frame->hDpi : 0x48; + temporary.dpi_y = frame->vDpi ? frame->vDpi : 0x48; + } else { + const std::uint32_t wantX = frame->hDpi == 0 ? temporary.dpi_x : frame->hDpi; + const std::uint32_t wantY = frame->vDpi == 0 ? temporary.dpi_y : frame->vDpi; + temporary.dpi_x = wantX != 0 ? 0x48 : 0; + temporary.dpi_y = wantY != 0 ? 0x48 : 0; + } + if ((frame->flags1 & STYLE_FRAME_FLAG_SLANT) != 0) temporary.slant_ratio = frame->slantRatio; + if ((frame->flags1 & STYLE_FRAME_FLAG_WEIGHT) != 0) { + temporary.effect_weight_x = frame->effectWeightX; + temporary.effect_weight_y = frame->effectWeightY; + } + return &temporary; +} + int Font::RenderCharGlyphImageCore(FontHandle handle, std::uint32_t code, FontRenderSurface* surface, float x, float y, FontGlyphMetrics* metrics, FontRenderOutput* result) { const auto fail = [&](int rc) { ClearRenderOutputs(metrics, result); @@ -451,13 +459,21 @@ int Font::RenderCharGlyphImageCore(FontHandle handle, std::uint32_t code, FontRe auto* font = GetNativeFont(handle); if (font->magic != HANDLE_MAGIC) return fail(SCE_FONT_ERROR_INVALID_FONT_HANDLE); if (!font->renderer) return fail(SCE_FONT_ERROR_NOT_BOUND_RENDERER); + StyleStateBlock surfaceStyle{}; + return RenderCodeWithStyle(handle, code, ResolveSurfaceStyleState(&font->cached_style.state, surface, surfaceStyle), surface, x, y, metrics, result); +} + +int Font::RenderCodeWithStyle(FontHandle handle, std::uint32_t code, const StyleStateBlock* style, FontRenderSurface* surface, float x, float y, FontGlyphMetrics* metrics, FontRenderOutput* result) { + const auto fail = [&](int rc) { + ClearRenderOutputs(metrics, result); + return rc; + }; + auto* font = GetNativeFont(handle); auto* library = static_cast(font->library); if (!library || library->magic != LIBRARY_MAGIC) return fail(SCE_FONT_ERROR_INVALID_FONT_HANDLE); const SysDriver* driver = library->sys_driver; if (!driver || !driver->glyph_index || !driver->set_char_with_dpi || !driver->set_char_default_dpi) return fail(SCE_FONT_ERROR_FATAL); const std::uint32_t modeLow = font->flags & 0x0Fu; - StyleStateBlock surfaceStyle{}; - const StyleStateBlock* style = ResolveSurfaceStyleState(&font->cached_style.state, surface, surfaceStyle); FontObj* obj = nullptr; std::uint32_t lockWord = 0; auto* entry = AcquireFontCtxEntry(FontContext(library, font), font->open_info.ctx_entry_index, modeLow, &obj, &lockWord); From 68820535b97287c1b451cf44fb3c7caa0008b26e Mon Sep 17 00:00:00 2001 From: Yunus Shaikh <75296019+EagleBearerYS@users.noreply.github.com> Date: Sun, 25 Oct 2026 07:27:44 +0000 Subject: [PATCH 2/4] feat(libSceFont): render generated glyphs with sceFontGlyphRenderImage Draw a glyph from sceFontGenerateCharGlyph through the sceFontRenderCharGlyphImage rasterizer at the glyph's own scale, with the renderer argument checked as sceFontBindRenderer checks it and the style frame argument, else the surface's frame, applied as surface frames are. The horizontal and vertical forms place the image like their character forms. The three functions build on RenderCodeWithStyle and ApplyStyleFrame from the previous commit and move out of Unimplemented.cpp. --- core/libs/prx/libSceFont/src/Render.cpp | 54 ++++++++++++ .../libs/prx/libSceFont/src/Unimplemented.cpp | 15 ---- core/libs/tests/GuestSystemFont.cpp | 87 +++++++++++++++++++ docs/dev/debt/libSceFont.md | 3 +- 4 files changed, 143 insertions(+), 16 deletions(-) diff --git a/core/libs/prx/libSceFont/src/Render.cpp b/core/libs/prx/libSceFont/src/Render.cpp index 876b21a590..fd2f3918b5 100644 --- a/core/libs/prx/libSceFont/src/Render.cpp +++ b/core/libs/prx/libSceFont/src/Render.cpp @@ -121,6 +121,48 @@ int RenderDirectional(FontHandle fontHandle, std::uint32_t code, FontRenderSurfa return rc; } +int RenderGlyph(FontGlyph glyph, const FontStyleFrame* styleFrame, FontRenderer renderer, FontRenderSurface* surface, float x, float y, FontGlyphMetrics* metrics, FontRenderOutput* result, std::uint16_t direction) { + const auto fail = [&](int rc) { + ClearRenderOutputs(metrics, result); + return rc; + }; + auto* generated = TryGetGeneratedGlyph(glyph); + if (!generated) return fail(SCE_FONT_ERROR_INVALID_GLYPH); + if (!renderer || static_cast(renderer)->magic != RENDERER_MAGIC) return fail(SCE_FONT_ERROR_INVALID_RENDERER); + if (styleFrame && !ValidStyleFrame(styleFrame)) return fail(SCE_FONT_ERROR_INVALID_PARAMETER); + if (!surface || !metrics || !result) return fail(SCE_FONT_ERROR_INVALID_PARAMETER); + const FontHandle owner = generated->owner; + auto* font = TryGetState(owner) ? GetNativeFont(owner) : nullptr; + std::uint32_t fontLock = 0; + if (!font || font->magic != HANDLE_MAGIC || !AcquireFontLock(font, fontLock)) return fail(SCE_FONT_ERROR_INVALID_FONT_HANDLE); + StyleStateBlock glyphStyle{}; + glyphStyle.scale_w = generated->glyph.scale_x; + glyphStyle.scale_h = generated->glyph.base_scale; + StyleStateBlock framed{}; + const StyleStateBlock* style = ApplyStyleFrame(&glyphStyle, styleFrame ? styleFrame : SurfaceStyleFrame(surface), framed); + float xUsed = x; + float yUsed = y; + int rc = SCE_FONT_OK; + if (direction == 0) { + const bool horizontal = static_cast(font->flags) >= 0; + if (horizontal) { + std::uint8_t layout[HORIZONTAL_LAYOUT_SIZE] = {}; + rc = ComputeHorizontalLayout(owner, style, layout); + yUsed = y + LoadFloat(layout, HORIZONTAL_BASELINE); + } else { + std::uint8_t layout[VERTICAL_LAYOUT_SIZE] = {}; + rc = ComputeVerticalLayout(owner, style, layout); + xUsed = x + LoadFloat(layout, VERTICAL_BASELINE_OFFSET_X); + } + direction = horizontal ? 1 : 2; + } + CachedStyleSetDirectionWord(font->cached_style, direction); + if (rc == SCE_FONT_OK) rc = RenderCodeWithStyle(owner, generated->codepoint, style, surface, xUsed, yUsed, metrics, result); + ReleaseFontLock(font, fontLock); + if (rc != SCE_FONT_OK) ClearRenderOutputs(metrics, result); + return rc; +} + } #pragma GCC visibility push(default) @@ -502,6 +544,18 @@ FontGlyphOutline* APS5_VABI sceFontGlyphRefersOutline(FontGlyph glyph) { return &generated->outline; } +int APS5_VABI sceFontGlyphRenderImage(FontGlyph glyph, const FontStyleFrame* styleFrame, FontRenderer renderer, FontRenderSurface* surface, float x, float y, FontGlyphMetrics* metrics, FontRenderOutput* output) { + return RenderGlyph(glyph, styleFrame, renderer, surface, x, y, metrics, output, 0); +} + +int APS5_VABI sceFontGlyphRenderImageHorizontal(FontGlyph glyph, const FontStyleFrame* styleFrame, FontRenderer renderer, FontRenderSurface* surface, float x, float y, FontGlyphMetrics* metrics, FontRenderOutput* output) { + return RenderGlyph(glyph, styleFrame, renderer, surface, x, y, metrics, output, 1); +} + +int APS5_VABI sceFontGlyphRenderImageVertical(FontGlyph glyph, const FontStyleFrame* styleFrame, FontRenderer renderer, FontRenderSurface* surface, float x, float y, FontGlyphMetrics* metrics, FontRenderOutput* output) { + return RenderGlyph(glyph, styleFrame, renderer, surface, x, y, metrics, output, 2); +} + } #pragma GCC visibility pop diff --git a/core/libs/prx/libSceFont/src/Unimplemented.cpp b/core/libs/prx/libSceFont/src/Unimplemented.cpp index e0dadb8ba9..41e61826f5 100644 --- a/core/libs/prx/libSceFont/src/Unimplemented.cpp +++ b/core/libs/prx/libSceFont/src/Unimplemented.cpp @@ -120,21 +120,6 @@ int APS5_VABI sceFontGetGlyphExpandBufferState(FontHandle font, FontGlyphExpandB return 0; } -int APS5_VABI sceFontGlyphRenderImage(FontGlyph glyph, const FontStyleFrame* styleFrame, FontRenderer renderer, FontRenderSurface* surface, float x, float y, FontGlyphMetrics* metrics, FontRenderOutput* output) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontGlyphRenderImageHorizontal(FontGlyph glyph, const FontStyleFrame* styleFrame, FontRenderer renderer, FontRenderSurface* surface, float x, float y, FontGlyphMetrics* metrics, FontRenderOutput* output) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontGlyphRenderImageVertical(FontGlyph glyph, const FontStyleFrame* styleFrame, FontRenderer renderer, FontRenderSurface* surface, float x, float y, FontGlyphMetrics* metrics, FontRenderOutput* output) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceFontGraphicsBeginFrame(FontGraphicsFrame* frame, std::uint64_t flags, FontGraphicsResource** resource) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/tests/GuestSystemFont.cpp b/core/libs/tests/GuestSystemFont.cpp index 5195bee16c..4b63b388a3 100644 --- a/core/libs/tests/GuestSystemFont.cpp +++ b/core/libs/tests/GuestSystemFont.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -175,6 +176,91 @@ static int RenderedPixels(FontHandle font, std::uint32_t code, float& advance) { return lit; } +struct GlyphImage { + int rc = SCE_FONT_OK; + std::vector pixels = std::vector(128 * 128); + FontGlyphMetrics metrics{}; + FontRenderOutput result{}; +}; + +template static GlyphImage RenderInto(TRender render) { + GlyphImage image; + FontRenderSurface surface{}; + sceFontRenderSurfaceInit(&surface, image.pixels.data(), 128, 1, 128, 128); + image.rc = render(&surface, &image.metrics, &image.result); + return image; +} + +static bool SameImage(const GlyphImage& left, const GlyphImage& right) { + int lit = 0; + for (const std::uint8_t pixel : left.pixels) lit += pixel != 0 ? 1 : 0; + const auto& a = left.result; + const auto& b = right.result; + const bool sameSurface = a.SurfaceImage.address - left.pixels.data() == b.SurfaceImage.address - right.pixels.data() && a.SurfaceImage.widthByte == b.SurfaceImage.widthByte && a.SurfaceImage.pixelSizeByte == b.SurfaceImage.pixelSizeByte && a.SurfaceImage.pixelFormat == b.SurfaceImage.pixelFormat; + const bool sameOutput = a.stage == b.stage && sameSurface && std::memcmp(&a.UpdateRect, &b.UpdateRect, sizeof(a.UpdateRect)) == 0 && std::memcmp(&a.ImageMetrics, &b.ImageMetrics, sizeof(a.ImageMetrics)) == 0; + return left.rc == SCE_FONT_OK && right.rc == SCE_FONT_OK && lit > 0 && left.pixels == right.pixels && std::memcmp(&left.metrics, &right.metrics, sizeof(left.metrics)) == 0 && sameOutput; +} + +static bool Cleared(const GlyphImage& image, int rc) { + const FontGlyphMetrics noMetrics{}; + const FontRenderOutput noResult{}; + return image.rc == rc && std::memcmp(&image.metrics, &noMetrics, sizeof(noMetrics)) == 0 && std::memcmp(&image.result, &noResult, sizeof(noResult)) == 0; +} + +static void CheckGlyphImages(FontHandle font, FontRenderer renderer) { + Require(sceFontBindRenderer(font, renderer) == SCE_FONT_OK); + Require(sceFontSetScalePixel(font, 40.0f, 40.0f) == SCE_FONT_OK); + Require(sceFontSetupRenderScalePixel(font, 40.0f, 40.0f) == SCE_FONT_OK); + FontGlyph glyph = nullptr; + Require(sceFontGenerateCharGlyph(font, 'A', nullptr, &glyph) == SCE_FONT_OK && glyph != nullptr); + + const auto horizontal = RenderInto([&](FontRenderSurface* surface, FontGlyphMetrics* metrics, FontRenderOutput* result) { return sceFontRenderCharGlyphImageHorizontal(font, 'A', surface, 10.0f, 100.0f, metrics, result); }); + Require(SameImage(horizontal, RenderInto([&](FontRenderSurface* surface, FontGlyphMetrics* metrics, FontRenderOutput* result) { return sceFontGlyphRenderImageHorizontal(glyph, nullptr, renderer, surface, 10.0f, 100.0f, metrics, result); }))); + const auto vertical = RenderInto([&](FontRenderSurface* surface, FontGlyphMetrics* metrics, FontRenderOutput* result) { return sceFontRenderCharGlyphImageVertical(font, 'A', surface, 60.0f, 10.0f, metrics, result); }); + Require(SameImage(vertical, RenderInto([&](FontRenderSurface* surface, FontGlyphMetrics* metrics, FontRenderOutput* result) { return sceFontGlyphRenderImageVertical(glyph, nullptr, renderer, surface, 60.0f, 10.0f, metrics, result); }))); + const auto written = RenderInto([&](FontRenderSurface* surface, FontGlyphMetrics* metrics, FontRenderOutput* result) { return sceFontRenderCharGlyphImage(font, 'A', surface, 10.0f, 20.0f, metrics, result); }); + Require(SameImage(written, RenderInto([&](FontRenderSurface* surface, FontGlyphMetrics* metrics, FontRenderOutput* result) { return sceFontGlyphRenderImage(glyph, nullptr, renderer, surface, 10.0f, 20.0f, metrics, result); }))); + + Require(sceFontSetupRenderScalePixel(font, 20.0f, 20.0f) == SCE_FONT_OK); + Require(sceFontSetScalePixel(font, 20.0f, 20.0f) == SCE_FONT_OK); + const auto smaller = RenderInto([&](FontRenderSurface* surface, FontGlyphMetrics* metrics, FontRenderOutput* result) { return sceFontRenderCharGlyphImageHorizontal(font, 'A', surface, 10.0f, 100.0f, metrics, result); }); + Require(smaller.rc == SCE_FONT_OK && smaller.pixels != horizontal.pixels); + Require(SameImage(horizontal, RenderInto([&](FontRenderSurface* surface, FontGlyphMetrics* metrics, FontRenderOutput* result) { return sceFontGlyphRenderImageHorizontal(glyph, nullptr, renderer, surface, 10.0f, 100.0f, metrics, result); }))); + FontStyleFrame frame{}; + Require(sceFontStyleFrameInit(&frame) == SCE_FONT_OK && sceFontStyleFrameSetScalePixel(&frame, 20.0f, 20.0f) == SCE_FONT_OK); + Require(SameImage(smaller, RenderInto([&](FontRenderSurface* surface, FontGlyphMetrics* metrics, FontRenderOutput* result) { return sceFontGlyphRenderImageHorizontal(glyph, &frame, renderer, surface, 10.0f, 100.0f, metrics, result); }))); + Require(SameImage(smaller, RenderInto([&](FontRenderSurface* surface, FontGlyphMetrics* metrics, FontRenderOutput* result) { + Require(sceFontRenderSurfaceSetStyleFrame(surface, &frame) == SCE_FONT_OK); + return sceFontGlyphRenderImageHorizontal(glyph, nullptr, renderer, surface, 10.0f, 100.0f, metrics, result); + }))); + + Require(sceFontUnbindRenderer(font) == SCE_FONT_OK); + Require(RenderInto([&](FontRenderSurface* surface, FontGlyphMetrics* metrics, FontRenderOutput* result) { return sceFontRenderCharGlyphImageHorizontal(font, 'A', surface, 10.0f, 100.0f, metrics, result); }).rc == SCE_FONT_ERROR_NOT_BOUND_RENDERER); + Require(SameImage(horizontal, RenderInto([&](FontRenderSurface* surface, FontGlyphMetrics* metrics, FontRenderOutput* result) { return sceFontGlyphRenderImageHorizontal(glyph, nullptr, renderer, surface, 10.0f, 100.0f, metrics, result); }))); + + const auto filled = [](FontGlyphMetrics* metrics, FontRenderOutput* result) { + std::memset(metrics, 0xA5, sizeof(*metrics)); + std::memset(result, 0xA5, sizeof(*result)); + }; + FontGlyphOpaque notAGlyph{}; + FontStyleFrame notAFrame{}; + std::uint64_t notARenderer = 0; + for (const FontGlyph wrong : {static_cast(nullptr), ¬AGlyph}) { + for (const auto render : {sceFontGlyphRenderImage, sceFontGlyphRenderImageHorizontal, sceFontGlyphRenderImageVertical}) { + Require(Cleared(RenderInto([&](FontRenderSurface* surface, FontGlyphMetrics* metrics, FontRenderOutput* result) { filled(metrics, result); return render(wrong, nullptr, renderer, surface, 10.0f, 100.0f, metrics, result); }), SCE_FONT_ERROR_INVALID_GLYPH)); + } + } + for (const auto render : {sceFontGlyphRenderImage, sceFontGlyphRenderImageHorizontal, sceFontGlyphRenderImageVertical}) { + Require(Cleared(RenderInto([&](FontRenderSurface*, FontGlyphMetrics* metrics, FontRenderOutput* result) { filled(metrics, result); return render(glyph, nullptr, renderer, nullptr, 10.0f, 100.0f, metrics, result); }), SCE_FONT_ERROR_INVALID_PARAMETER)); + Require(Cleared(RenderInto([&](FontRenderSurface* surface, FontGlyphMetrics*, FontRenderOutput* result) { std::memset(result, 0xA5, sizeof(*result)); return render(glyph, nullptr, renderer, surface, 10.0f, 100.0f, nullptr, result); }), SCE_FONT_ERROR_INVALID_PARAMETER)); + Require(Cleared(RenderInto([&](FontRenderSurface* surface, FontGlyphMetrics* metrics, FontRenderOutput*) { std::memset(metrics, 0xA5, sizeof(*metrics)); return render(glyph, nullptr, renderer, surface, 10.0f, 100.0f, metrics, nullptr); }), SCE_FONT_ERROR_INVALID_PARAMETER)); + Require(Cleared(RenderInto([&](FontRenderSurface* surface, FontGlyphMetrics* metrics, FontRenderOutput* result) { filled(metrics, result); return render(glyph, nullptr, nullptr, surface, 10.0f, 100.0f, metrics, result); }), SCE_FONT_ERROR_INVALID_RENDERER)); + Require(Cleared(RenderInto([&](FontRenderSurface* surface, FontGlyphMetrics* metrics, FontRenderOutput* result) { filled(metrics, result); return render(glyph, nullptr, ¬ARenderer, surface, 10.0f, 100.0f, metrics, result); }), SCE_FONT_ERROR_INVALID_RENDERER)); + Require(Cleared(RenderInto([&](FontRenderSurface* surface, FontGlyphMetrics* metrics, FontRenderOutput* result) { filled(metrics, result); return render(glyph, ¬AFrame, renderer, surface, 10.0f, 100.0f, metrics, result); }), SCE_FONT_ERROR_INVALID_PARAMETER)); + } + Require(sceFontDeleteGlyph(nullptr, &glyph) == SCE_FONT_OK && glyph == nullptr); +} + int main() { constexpr std::uint32_t EuropeanLight = 0x180700C3u; constexpr std::uint32_t EuropeanBold = 0x180700C7u; @@ -310,6 +396,7 @@ int main() { Require(sceFontGetKerning(mixed, 'A', 0x4E00, &kerning) == SCE_FONT_OK && kerning.offsetX == 0.0f); FontRenderer renderer = nullptr; Require(sceFontCreateRenderer(&memory, sceFontSelectRendererFt(0), &renderer) == SCE_FONT_OK); + CheckGlyphImages(memoryFont, renderer); Require(sceFontBindRenderer(mixed, renderer) == SCE_FONT_OK); Require(sceFontSetupRenderScalePixel(mixed, 100.0f, 100.0f) == SCE_FONT_OK); float advance = 0.0f; diff --git a/docs/dev/debt/libSceFont.md b/docs/dev/debt/libSceFont.md index 7837f059cc..ffdbf25290 100644 --- a/docs/dev/debt/libSceFont.md +++ b/docs/dev/debt/libSceFont.md @@ -18,6 +18,7 @@ - [sceFontCharactersRefersTextCodes, sceFontTextCodesStepNext and sceFontTextCodesStepBack](../../../core/libs/prx/libSceFont/src/Text.cpp) (libSceFont) - the signatures follow the [Orbital](https://github.com/AlexAltea/orbital/blob/cc408367da6a6ebbf5ac1ac559fefeadc73b2831/tools/ida/db_types.json#L679) IDA type database and [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Font/SceFont.Engine.cs#L395-L406), and the 64-byte `FontTextCodes` with the order pointer at 0 and the code at 8 follows SharpProspero's [SceFontTextCodes](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Font/SceFontStructs.cs#L216-L228). The traversal follows the PS4 11.00 `libSceFont.sprx` disassembly posted in [#4050](https://github.com/boykopovar/AnyPS5/pull/4050#issuecomment-6114504202), not a PS5 measurement: the cursor keeps the current, start and term characters at +0x28, +0x30 and +0x38; `Refers` never compares the character with the term and returns null for a clustered or synthetic character; `StepNext` returns null at the term after visiting it; `StepBack` returns null when the current character is the start; both steps skip synthetic characters. Assumed and not covered by the report, which says `StepNext` returns null only at the term: `StepNext` also returns null at the end of the chain (for example with a null term), and `StepBack` at the head of the chain. A null cursor or output returns null as in [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/d2413fc2ebd91d1b7234197c2dd5bd48e1cb6a51/src/libs/libFont.cpp#L1696-L1740); a null text character throws, as its console result is unknown - [sceFontDefineAttribute, sceFontGetAttribute](../../../core/libs/prx/libSceFont/src/Style.cpp), [sceFontGlyphDefineAttribute, sceFontGlyphGetAttribute](../../../core/libs/prx/libSceFont/src/Render.cpp) (libSceFont) - the signatures `(handle, int attribute, int* oldAttribute)` and `(handle, int attribute, int* nowAttribute)`, and the same shape for a glyph, follow the [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Font/SceFont.Engine.cs) bindings, matching the IDA type database of [Orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json) and [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/d2413fc2ebd91d1b7234197c2dd5bd48e1cb6a51/src/libs/libFont.cpp#L1249-L1264), not confirmed on a title. Every handle and every glyph keeps one attribute, an opaque int whose meaning and valid values are unknown: define stores the new value and returns the previous one, and get returns the stored value with its `attribute` argument unused, as in KytyPS5. An attribute never set is 0. The null checks and the invalid-object errors follow the other libSceFont functions and diverge from [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/d2413fc2ebd91d1b7234197c2dd5bd48e1cb6a51/src/libs/libFont.cpp#L1251-L1265), which returns `OK` for a null handle or glyph after writing 0 to the old-attribute pointer: every other handle and glyph entry point in this library reports the invalid object instead, and a title that passes one is already failing. shadPS4 leaves both exports unimplemented, so there is no second implementation to compare. PPSA01325 calls the glyph define with 0x11 on a glyph from `sceFontGenerateCharGlyph` that it deletes unused and draws with `sceFontRenderCharGlyphImageHorizontal`; the three bindings all read the third argument as an out pointer, not a value, so this call sets the glyph's attribute to 0x11. The attribute changes neither shaping nor rendering. +- [sceFontGlyphRenderImage, sceFontGlyphRenderImageHorizontal, sceFontGlyphRenderImageVertical](../../../core/libs/prx/libSceFont/src/Render.cpp) (libSceFont) - unchecked on a console or a title, and [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/font/font.cpp) only stubs them. They draw the glyph's character from its owner font the way `sceFontRenderCharGlyphImage` and its directional forms do, at the scale the glyph was generated with, as `sceFontGlyphRefersOutline` uses it. The renderer argument stands in for the bound renderer and is checked as in `sceFontBindRenderer`; the style frame argument, else the surface's frame, is applied as a surface frame is in `sceFontRenderCharGlyphImage`, and an invalid one returns `SCE_FONT_ERROR_INVALID_PARAMETER`. Assumed: the argument frame wins over the surface's, the error order, `SCE_FONT_ERROR_INVALID_FONT_HANDLE` once the owner font is closed, and the generic form taking its direction from the owner font ### Functional @@ -32,7 +33,7 @@ Frame lifecycle, drawing completion, device usage, frame policy, clipping and fill setup/update argument signatures in [Unimplemented.cpp](../../../core/libs/prx/libSceFont/src/Unimplemented.cpp) are recovered. Frame and resource layouts remain opaque; their runtime implementation is still unavailable. -Glyph image rendering uses six pointer arguments and two scalar coordinates, with existing style, surface, metrics and output layouts. Positioning takes a mask and two floats. Control, word-character search and resource release/exchange/render signatures in [Unimplemented.cpp](../../../core/libs/prx/libSceFont/src/Unimplemented.cpp) are recovered; control payload types depend on the command and remain unmodeled. +Positioning takes a mask and two floats. Control, word-character search and resource release/exchange/render signatures in [Unimplemented.cpp](../../../core/libs/prx/libSceFont/src/Unimplemented.cpp) are recovered; control payload types depend on the command and remain unmodeled. Canvas, design, surface texture and fill-pattern construction signatures in [Unimplemented.cpp](../../../core/libs/prx/libSceFont/src/Unimplemented.cpp) are recovered. Pattern mode/count semantics and the graphics surface descriptor fields still require reconstruction; distinct opaque types preserve the confirmed object categories. From 380803b26dc6a586be22b020f573f6c7c54b914c Mon Sep 17 00:00:00 2001 From: Yunus Shaikh <75296019+EagleBearerYS@users.noreply.github.com> Date: Sun, 25 Oct 2026 07:16:23 -0700 Subject: [PATCH 3/4] test(libSceFont): compare glyph image surface offsets as checked integers SameImage subtracted each render output's surface address from its own pixel buffer's pointer. Convert both to integers instead, check that the update rectangle starting at that address lies inside the buffer, and compare the offsets. --- core/libs/tests/GuestSystemFont.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/core/libs/tests/GuestSystemFont.cpp b/core/libs/tests/GuestSystemFont.cpp index 4b63b388a3..b09ed91645 100644 --- a/core/libs/tests/GuestSystemFont.cpp +++ b/core/libs/tests/GuestSystemFont.cpp @@ -191,12 +191,25 @@ template static GlyphImage RenderInto(TRender render) { return image; } +static bool SurfaceOffset(const GlyphImage& image, std::uintptr_t& offset) { + const auto& surface = image.result.SurfaceImage; + const auto& rect = image.result.UpdateRect; + const auto base = reinterpret_cast(image.pixels.data()); + const auto address = reinterpret_cast(surface.address); + const std::uintptr_t span = rect.w == 0 || rect.h == 0 ? 0 : static_cast(rect.h - 1) * surface.widthByte + static_cast(rect.w) * surface.pixelSizeByte; + if (address < base || address - base > image.pixels.size() || span > image.pixels.size() - (address - base)) return false; + offset = address - base; + return true; +} + static bool SameImage(const GlyphImage& left, const GlyphImage& right) { int lit = 0; for (const std::uint8_t pixel : left.pixels) lit += pixel != 0 ? 1 : 0; const auto& a = left.result; const auto& b = right.result; - const bool sameSurface = a.SurfaceImage.address - left.pixels.data() == b.SurfaceImage.address - right.pixels.data() && a.SurfaceImage.widthByte == b.SurfaceImage.widthByte && a.SurfaceImage.pixelSizeByte == b.SurfaceImage.pixelSizeByte && a.SurfaceImage.pixelFormat == b.SurfaceImage.pixelFormat; + std::uintptr_t leftOffset = 0; + std::uintptr_t rightOffset = 0; + const bool sameSurface = SurfaceOffset(left, leftOffset) && SurfaceOffset(right, rightOffset) && leftOffset == rightOffset && a.SurfaceImage.widthByte == b.SurfaceImage.widthByte && a.SurfaceImage.pixelSizeByte == b.SurfaceImage.pixelSizeByte && a.SurfaceImage.pixelFormat == b.SurfaceImage.pixelFormat; const bool sameOutput = a.stage == b.stage && sameSurface && std::memcmp(&a.UpdateRect, &b.UpdateRect, sizeof(a.UpdateRect)) == 0 && std::memcmp(&a.ImageMetrics, &b.ImageMetrics, sizeof(a.ImageMetrics)) == 0; return left.rc == SCE_FONT_OK && right.rc == SCE_FONT_OK && lit > 0 && left.pixels == right.pixels && std::memcmp(&left.metrics, &right.metrics, sizeof(left.metrics)) == 0 && sameOutput; } From 7d86f79ebe42d5751435d9802a9a556b9a8ca7b8 Mon Sep 17 00:00:00 2001 From: Yunus Shaikh <75296019+EagleBearerYS@users.noreply.github.com> Date: Mon, 26 Oct 2026 05:04:51 -0700 Subject: [PATCH 4/4] fix(libSceFont): follow the module's frame order and leave vertical glyph renders unimplemented BunnyFl ran firmware 13.42's libSceFont natively against this PR's checks. With both set, the surface's style frame wins over the argument, so it is now taken first. A frame that sets no scale, zeroed or only initialised, is accepted and keeps the glyph's generation scale instead of returning SCE_FONT_ERROR_INVALID_PARAMETER. A valid sceFontGlyphRenderImageVertical call, and sceFontGlyphRenderImage on a vertical font, throw again: the module draws the char call's pixels there but reports a different advance and stride (40/40 against 90/90 at 40 px), and one case doesn't name the rule. The test drops the vertical comparison and the non-renderer pointer check, whose result in the module depends on memory past the pointer, and checks the frame order and the unscaled frames. --- core/libs/prx/libSceFont/src/Render.cpp | 26 +++++++++++------------ core/libs/tests/GuestSystemFont.cpp | 28 +++++++++++++++++++------ docs/dev/debt/libSceFont.md | 2 +- 3 files changed, 35 insertions(+), 21 deletions(-) diff --git a/core/libs/prx/libSceFont/src/Render.cpp b/core/libs/prx/libSceFont/src/Render.cpp index fd2f3918b5..af88e20e49 100644 --- a/core/libs/prx/libSceFont/src/Render.cpp +++ b/core/libs/prx/libSceFont/src/Render.cpp @@ -13,6 +13,7 @@ #include FT_FREETYPE_H #include "prx/libSceFont/include/FontInternal.hpp" +#include "prx/libc/include/General.hpp" namespace { @@ -129,8 +130,8 @@ int RenderGlyph(FontGlyph glyph, const FontStyleFrame* styleFrame, FontRenderer auto* generated = TryGetGeneratedGlyph(glyph); if (!generated) return fail(SCE_FONT_ERROR_INVALID_GLYPH); if (!renderer || static_cast(renderer)->magic != RENDERER_MAGIC) return fail(SCE_FONT_ERROR_INVALID_RENDERER); - if (styleFrame && !ValidStyleFrame(styleFrame)) return fail(SCE_FONT_ERROR_INVALID_PARAMETER); if (!surface || !metrics || !result) return fail(SCE_FONT_ERROR_INVALID_PARAMETER); + if (direction == 2) NotImplemented_nid_no_patch("sceFontGlyphRenderImageVertical"); const FontHandle owner = generated->owner; auto* font = TryGetState(owner) ? GetNativeFont(owner) : nullptr; std::uint32_t fontLock = 0; @@ -139,25 +140,22 @@ int RenderGlyph(FontGlyph glyph, const FontStyleFrame* styleFrame, FontRenderer glyphStyle.scale_w = generated->glyph.scale_x; glyphStyle.scale_h = generated->glyph.base_scale; StyleStateBlock framed{}; - const StyleStateBlock* style = ApplyStyleFrame(&glyphStyle, styleFrame ? styleFrame : SurfaceStyleFrame(surface), framed); - float xUsed = x; + const FontStyleFrame* surfaceFrame = SurfaceStyleFrame(surface); + const StyleStateBlock* style = ApplyStyleFrame(&glyphStyle, surfaceFrame ? surfaceFrame : styleFrame, framed); float yUsed = y; int rc = SCE_FONT_OK; if (direction == 0) { - const bool horizontal = static_cast(font->flags) >= 0; - if (horizontal) { - std::uint8_t layout[HORIZONTAL_LAYOUT_SIZE] = {}; - rc = ComputeHorizontalLayout(owner, style, layout); - yUsed = y + LoadFloat(layout, HORIZONTAL_BASELINE); - } else { - std::uint8_t layout[VERTICAL_LAYOUT_SIZE] = {}; - rc = ComputeVerticalLayout(owner, style, layout); - xUsed = x + LoadFloat(layout, VERTICAL_BASELINE_OFFSET_X); + if (static_cast(font->flags) < 0) { + ReleaseFontLock(font, fontLock); + NotImplemented_nid_no_patch("sceFontGlyphRenderImage: a vertical font"); } - direction = horizontal ? 1 : 2; + std::uint8_t layout[HORIZONTAL_LAYOUT_SIZE] = {}; + rc = ComputeHorizontalLayout(owner, style, layout); + yUsed = y + LoadFloat(layout, HORIZONTAL_BASELINE); + direction = 1; } CachedStyleSetDirectionWord(font->cached_style, direction); - if (rc == SCE_FONT_OK) rc = RenderCodeWithStyle(owner, generated->codepoint, style, surface, xUsed, yUsed, metrics, result); + if (rc == SCE_FONT_OK) rc = RenderCodeWithStyle(owner, generated->codepoint, style, surface, x, yUsed, metrics, result); ReleaseFontLock(font, fontLock); if (rc != SCE_FONT_OK) ClearRenderOutputs(metrics, result); return rc; diff --git a/core/libs/tests/GuestSystemFont.cpp b/core/libs/tests/GuestSystemFont.cpp index b09ed91645..d12bebc887 100644 --- a/core/libs/tests/GuestSystemFont.cpp +++ b/core/libs/tests/GuestSystemFont.cpp @@ -229,8 +229,6 @@ static void CheckGlyphImages(FontHandle font, FontRenderer renderer) { const auto horizontal = RenderInto([&](FontRenderSurface* surface, FontGlyphMetrics* metrics, FontRenderOutput* result) { return sceFontRenderCharGlyphImageHorizontal(font, 'A', surface, 10.0f, 100.0f, metrics, result); }); Require(SameImage(horizontal, RenderInto([&](FontRenderSurface* surface, FontGlyphMetrics* metrics, FontRenderOutput* result) { return sceFontGlyphRenderImageHorizontal(glyph, nullptr, renderer, surface, 10.0f, 100.0f, metrics, result); }))); - const auto vertical = RenderInto([&](FontRenderSurface* surface, FontGlyphMetrics* metrics, FontRenderOutput* result) { return sceFontRenderCharGlyphImageVertical(font, 'A', surface, 60.0f, 10.0f, metrics, result); }); - Require(SameImage(vertical, RenderInto([&](FontRenderSurface* surface, FontGlyphMetrics* metrics, FontRenderOutput* result) { return sceFontGlyphRenderImageVertical(glyph, nullptr, renderer, surface, 60.0f, 10.0f, metrics, result); }))); const auto written = RenderInto([&](FontRenderSurface* surface, FontGlyphMetrics* metrics, FontRenderOutput* result) { return sceFontRenderCharGlyphImage(font, 'A', surface, 10.0f, 20.0f, metrics, result); }); Require(SameImage(written, RenderInto([&](FontRenderSurface* surface, FontGlyphMetrics* metrics, FontRenderOutput* result) { return sceFontGlyphRenderImage(glyph, nullptr, renderer, surface, 10.0f, 20.0f, metrics, result); }))); @@ -246,6 +244,28 @@ static void CheckGlyphImages(FontHandle font, FontRenderer renderer) { Require(sceFontRenderSurfaceSetStyleFrame(surface, &frame) == SCE_FONT_OK); return sceFontGlyphRenderImageHorizontal(glyph, nullptr, renderer, surface, 10.0f, 100.0f, metrics, result); }))); + FontStyleFrame larger{}; + Require(sceFontStyleFrameInit(&larger) == SCE_FONT_OK && sceFontStyleFrameSetScalePixel(&larger, 30.0f, 30.0f) == SCE_FONT_OK); + const auto surfaceLarger = RenderInto([&](FontRenderSurface* surface, FontGlyphMetrics* metrics, FontRenderOutput* result) { + Require(sceFontRenderSurfaceSetStyleFrame(surface, &larger) == SCE_FONT_OK); + return sceFontGlyphRenderImageHorizontal(glyph, nullptr, renderer, surface, 10.0f, 100.0f, metrics, result); + }); + Require(surfaceLarger.rc == SCE_FONT_OK && surfaceLarger.pixels != smaller.pixels && surfaceLarger.pixels != horizontal.pixels); + Require(SameImage(surfaceLarger, RenderInto([&](FontRenderSurface* surface, FontGlyphMetrics* metrics, FontRenderOutput* result) { + Require(sceFontRenderSurfaceSetStyleFrame(surface, &larger) == SCE_FONT_OK); + return sceFontGlyphRenderImageHorizontal(glyph, &frame, renderer, surface, 10.0f, 100.0f, metrics, result); + }))); + Require(SameImage(smaller, RenderInto([&](FontRenderSurface* surface, FontGlyphMetrics* metrics, FontRenderOutput* result) { + Require(sceFontRenderSurfaceSetStyleFrame(surface, &frame) == SCE_FONT_OK); + return sceFontGlyphRenderImageHorizontal(glyph, &larger, renderer, surface, 10.0f, 100.0f, metrics, result); + }))); + FontStyleFrame notAFrame{}; + FontStyleFrame initialized{}; + Require(sceFontStyleFrameInit(&initialized) == SCE_FONT_OK); + for (const FontStyleFrame* unscaled : {¬AFrame, &initialized}) { + Require(SameImage(horizontal, RenderInto([&](FontRenderSurface* surface, FontGlyphMetrics* metrics, FontRenderOutput* result) { return sceFontGlyphRenderImageHorizontal(glyph, unscaled, renderer, surface, 10.0f, 100.0f, metrics, result); }))); + Require(SameImage(written, RenderInto([&](FontRenderSurface* surface, FontGlyphMetrics* metrics, FontRenderOutput* result) { return sceFontGlyphRenderImage(glyph, unscaled, renderer, surface, 10.0f, 20.0f, metrics, result); }))); + } Require(sceFontUnbindRenderer(font) == SCE_FONT_OK); Require(RenderInto([&](FontRenderSurface* surface, FontGlyphMetrics* metrics, FontRenderOutput* result) { return sceFontRenderCharGlyphImageHorizontal(font, 'A', surface, 10.0f, 100.0f, metrics, result); }).rc == SCE_FONT_ERROR_NOT_BOUND_RENDERER); @@ -256,8 +276,6 @@ static void CheckGlyphImages(FontHandle font, FontRenderer renderer) { std::memset(result, 0xA5, sizeof(*result)); }; FontGlyphOpaque notAGlyph{}; - FontStyleFrame notAFrame{}; - std::uint64_t notARenderer = 0; for (const FontGlyph wrong : {static_cast(nullptr), ¬AGlyph}) { for (const auto render : {sceFontGlyphRenderImage, sceFontGlyphRenderImageHorizontal, sceFontGlyphRenderImageVertical}) { Require(Cleared(RenderInto([&](FontRenderSurface* surface, FontGlyphMetrics* metrics, FontRenderOutput* result) { filled(metrics, result); return render(wrong, nullptr, renderer, surface, 10.0f, 100.0f, metrics, result); }), SCE_FONT_ERROR_INVALID_GLYPH)); @@ -268,8 +286,6 @@ static void CheckGlyphImages(FontHandle font, FontRenderer renderer) { Require(Cleared(RenderInto([&](FontRenderSurface* surface, FontGlyphMetrics*, FontRenderOutput* result) { std::memset(result, 0xA5, sizeof(*result)); return render(glyph, nullptr, renderer, surface, 10.0f, 100.0f, nullptr, result); }), SCE_FONT_ERROR_INVALID_PARAMETER)); Require(Cleared(RenderInto([&](FontRenderSurface* surface, FontGlyphMetrics* metrics, FontRenderOutput*) { std::memset(metrics, 0xA5, sizeof(*metrics)); return render(glyph, nullptr, renderer, surface, 10.0f, 100.0f, metrics, nullptr); }), SCE_FONT_ERROR_INVALID_PARAMETER)); Require(Cleared(RenderInto([&](FontRenderSurface* surface, FontGlyphMetrics* metrics, FontRenderOutput* result) { filled(metrics, result); return render(glyph, nullptr, nullptr, surface, 10.0f, 100.0f, metrics, result); }), SCE_FONT_ERROR_INVALID_RENDERER)); - Require(Cleared(RenderInto([&](FontRenderSurface* surface, FontGlyphMetrics* metrics, FontRenderOutput* result) { filled(metrics, result); return render(glyph, nullptr, ¬ARenderer, surface, 10.0f, 100.0f, metrics, result); }), SCE_FONT_ERROR_INVALID_RENDERER)); - Require(Cleared(RenderInto([&](FontRenderSurface* surface, FontGlyphMetrics* metrics, FontRenderOutput* result) { filled(metrics, result); return render(glyph, ¬AFrame, renderer, surface, 10.0f, 100.0f, metrics, result); }), SCE_FONT_ERROR_INVALID_PARAMETER)); } Require(sceFontDeleteGlyph(nullptr, &glyph) == SCE_FONT_OK && glyph == nullptr); } diff --git a/docs/dev/debt/libSceFont.md b/docs/dev/debt/libSceFont.md index ffdbf25290..303f048718 100644 --- a/docs/dev/debt/libSceFont.md +++ b/docs/dev/debt/libSceFont.md @@ -18,7 +18,7 @@ - [sceFontCharactersRefersTextCodes, sceFontTextCodesStepNext and sceFontTextCodesStepBack](../../../core/libs/prx/libSceFont/src/Text.cpp) (libSceFont) - the signatures follow the [Orbital](https://github.com/AlexAltea/orbital/blob/cc408367da6a6ebbf5ac1ac559fefeadc73b2831/tools/ida/db_types.json#L679) IDA type database and [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Font/SceFont.Engine.cs#L395-L406), and the 64-byte `FontTextCodes` with the order pointer at 0 and the code at 8 follows SharpProspero's [SceFontTextCodes](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Font/SceFontStructs.cs#L216-L228). The traversal follows the PS4 11.00 `libSceFont.sprx` disassembly posted in [#4050](https://github.com/boykopovar/AnyPS5/pull/4050#issuecomment-6114504202), not a PS5 measurement: the cursor keeps the current, start and term characters at +0x28, +0x30 and +0x38; `Refers` never compares the character with the term and returns null for a clustered or synthetic character; `StepNext` returns null at the term after visiting it; `StepBack` returns null when the current character is the start; both steps skip synthetic characters. Assumed and not covered by the report, which says `StepNext` returns null only at the term: `StepNext` also returns null at the end of the chain (for example with a null term), and `StepBack` at the head of the chain. A null cursor or output returns null as in [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/d2413fc2ebd91d1b7234197c2dd5bd48e1cb6a51/src/libs/libFont.cpp#L1696-L1740); a null text character throws, as its console result is unknown - [sceFontDefineAttribute, sceFontGetAttribute](../../../core/libs/prx/libSceFont/src/Style.cpp), [sceFontGlyphDefineAttribute, sceFontGlyphGetAttribute](../../../core/libs/prx/libSceFont/src/Render.cpp) (libSceFont) - the signatures `(handle, int attribute, int* oldAttribute)` and `(handle, int attribute, int* nowAttribute)`, and the same shape for a glyph, follow the [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Font/SceFont.Engine.cs) bindings, matching the IDA type database of [Orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json) and [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/d2413fc2ebd91d1b7234197c2dd5bd48e1cb6a51/src/libs/libFont.cpp#L1249-L1264), not confirmed on a title. Every handle and every glyph keeps one attribute, an opaque int whose meaning and valid values are unknown: define stores the new value and returns the previous one, and get returns the stored value with its `attribute` argument unused, as in KytyPS5. An attribute never set is 0. The null checks and the invalid-object errors follow the other libSceFont functions and diverge from [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/d2413fc2ebd91d1b7234197c2dd5bd48e1cb6a51/src/libs/libFont.cpp#L1251-L1265), which returns `OK` for a null handle or glyph after writing 0 to the old-attribute pointer: every other handle and glyph entry point in this library reports the invalid object instead, and a title that passes one is already failing. shadPS4 leaves both exports unimplemented, so there is no second implementation to compare. PPSA01325 calls the glyph define with 0x11 on a glyph from `sceFontGenerateCharGlyph` that it deletes unused and draws with `sceFontRenderCharGlyphImageHorizontal`; the three bindings all read the third argument as an out pointer, not a value, so this call sets the glyph's attribute to 0x11. The attribute changes neither shaping nor rendering. -- [sceFontGlyphRenderImage, sceFontGlyphRenderImageHorizontal, sceFontGlyphRenderImageVertical](../../../core/libs/prx/libSceFont/src/Render.cpp) (libSceFont) - unchecked on a console or a title, and [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/font/font.cpp) only stubs them. They draw the glyph's character from its owner font the way `sceFontRenderCharGlyphImage` and its directional forms do, at the scale the glyph was generated with, as `sceFontGlyphRefersOutline` uses it. The renderer argument stands in for the bound renderer and is checked as in `sceFontBindRenderer`; the style frame argument, else the surface's frame, is applied as a surface frame is in `sceFontRenderCharGlyphImage`, and an invalid one returns `SCE_FONT_ERROR_INVALID_PARAMETER`. Assumed: the argument frame wins over the surface's, the error order, `SCE_FONT_ERROR_INVALID_FONT_HANDLE` once the owner font is closed, and the generic form taking its direction from the owner font +- [sceFontGlyphRenderImage, sceFontGlyphRenderImageHorizontal, sceFontGlyphRenderImageVertical](../../../core/libs/prx/libSceFont/src/Render.cpp) (libSceFont) - follow firmware 13.42's module as BunnyFl ran it natively ([comment](https://github.com/boykopovar/AnyPS5/pull/10892#issuecomment-6415399922); one font, 'A', one pixel format, not a console): the glyph's character is drawn as `sceFontRenderCharGlyphImage` and its directional forms draw it, at the glyph's generation scale; the surface's style frame wins over the argument, and a frame that sets no scale keeps that scale. A valid `sceFontGlyphRenderImageVertical` call, and `sceFontGlyphRenderImage` on a vertical font, throw: the module reports advance and stride 40/40 there where the char call gives 90/90 at 40 px, and one case doesn't name the rule. A pointer that isn't a renderer, and `SCE_FONT_ERROR_INVALID_FONT_HANDLE` for a closed owner font, are not measured ### Functional