From c68ebe19fb5090ce85fcc897e3d6e6af7e6a9c64 Mon Sep 17 00:00:00 2001 From: tarfouss3 <149015782+tarfouss3@users.noreply.github.com> Date: Fri, 9 Oct 2026 21:25:15 +0000 Subject: [PATCH 1/2] feat(libSceFont): implement attributes and text code stepping Implements 6 exports that called NotImplemented: sceFontDefineAttribute, sceFontGetAttribute, sceFontCharactersRefersTextCodes, sceFontTextCodesStepNext, sceFontTextCodesStepBack, sceFontCharacterGetSyllableStringState. Left as stubs, as no reference establishes their signature or behaviour: sceFontControl, sceFontCreateWords, sceFontDestroyWords, sceFontWordsFindWordCharacters, sceFontGetFontGlyphsOutlineProfile, sceFontGetFontMetrics, sceFontGetFontStyleInformation, sceFontGetGlyphExpandBufferState, sceFontSupportGlyphs, sceFontGetScriptLanguage, sceFontSetScriptLanguage, sceFontGetTypographicDesign, sceFontSetTypographicDesign, sceFontGlyphGetAttribute, sceFontGlyphRenderImage, sceFontGlyphRenderImageHorizontal, sceFontGlyphRenderImageVertical, sceFontCreateGraphicsDevice, sceFontCreateGraphicsService, sceFontCreateGraphicsServiceWithEdition, sceFontDestroyGraphicsDevice, sceFontDestroyGraphicsService, sceFontGraphicsBeginFrame, sceFontGraphicsDrawingCancel, sceFontGraphicsDrawingFinish, sceFontGraphicsEndFrame, sceFontGraphicsExchangeResource, sceFontGraphicsGetDeviceUsage, sceFontGraphicsRegionInit, sceFontGraphicsRegionInitCircular, sceFontGraphicsRegionInitRoundish, sceFontGraphicsRelease, sceFontGraphicsRenderResource, sceFontGraphicsSetFramePolicy, sceFontGraphicsStructureCanvas, sceFontGraphicsStructureCanvasSequence, sceFontGraphicsStructureDesign, sceFontGraphicsStructureDesignResource, sceFontGraphicsStructureSurfaceTexture, sceFontGraphicsDrawupFillTextureImageObject, sceFontGraphicsDrawupFillTexturePatternObject, sceFontGraphicsFillMethodInit, sceFontGraphicsFillPlotInit, sceFontGraphicsFillPlotSetLayout, sceFontGraphicsFillPlotSetMapping, sceFontGraphicsFillRatesInit, sceFontGraphicsFillRatesSetFillEffect, sceFontGraphicsFillRatesSetLayout, sceFontGraphicsFillRatesSetMapping, sceFontGraphicsSetupClipping, sceFontGraphicsSetupColorRates, sceFontGraphicsSetupFillMethod, sceFontGraphicsSetupFillRates, sceFontGraphicsSetupGlyphFill, sceFontGraphicsSetupGlyphFillPlot, sceFontGraphicsSetupHandleDefault, sceFontGraphicsSetupLocation, sceFontGraphicsSetupPositioning, sceFontGraphicsSetupRotation, sceFontGraphicsSetupScaling, sceFontGraphicsSetupShapeFill, sceFontGraphicsSetupShapeFillPlot, sceFontGraphicsUpdateClipping, sceFontGraphicsUpdateColorRates, sceFontGraphicsUpdateFillMethod, sceFontGraphicsUpdateFillRates, sceFontGraphicsUpdateGlyphFill, sceFontGraphicsUpdateGlyphFillPlot, sceFontGraphicsUpdateLocation, sceFontGraphicsUpdatePositioning, sceFontGraphicsUpdateRotation, sceFontGraphicsUpdateScaling, sceFontGraphicsUpdateShapeFill, sceFontGraphicsUpdateShapeFillPlot, sceFontFtInitAliases, sceFontFtSetAliasFont, sceFontFtSetAliasPath, sceFontFtSupportBdf, sceFontFtSupportCid, sceFontFtSupportFontFormats, sceFontFtSupportOpenType, sceFontFtSupportOpenTypeOtf, sceFontFtSupportOpenTypeTtf, sceFontFtSupportPcf, sceFontFtSupportPfr, sceFontFtSupportSystemFonts, sceFontFtSupportTrueType, sceFontFtSupportTrueTypeGx, sceFontFtSupportType1, sceFontFtSupportType42, sceFontFtSupportWinFonts, sceFontFtTermAliases, sceFontSelectGlyphsFt. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01CM3ZvU1gP32iEs7abAt7N6 --- core/libs/prx/libSceFont/CMakeLists.txt | 10 ++ .../libs/prx/libSceFont/include/FontTypes.hpp | 9 ++ core/libs/prx/libSceFont/src/Style.cpp | 86 +++++++++++ core/libs/prx/libSceFont/src/Text.cpp | 87 +++++++++++ .../libs/prx/libSceFont/src/Unimplemented.cpp | 30 ---- core/libs/tests/GuestFont.cpp | 62 +++++++- core/libs/tests/GuestFontText.cpp | 144 ++++++++++++++++++ docs/dev/TechnicalDebt.md | 6 + 8 files changed, 401 insertions(+), 33 deletions(-) create mode 100644 core/libs/tests/GuestFontText.cpp diff --git a/core/libs/prx/libSceFont/CMakeLists.txt b/core/libs/prx/libSceFont/CMakeLists.txt index 458e3e5fd75..01e023e7433 100644 --- a/core/libs/prx/libSceFont/CMakeLists.txt +++ b/core/libs/prx/libSceFont/CMakeLists.txt @@ -5,6 +5,7 @@ if(BUILD_TESTING) add_test_executable(guest_font_tests ${LIBS_INCLUDE_DIR}/tests/GuestFont.cpp) target_include_directories(guest_font_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_font_tests PRIVATE libSceFont libSceFontFt libc) + configure_windows_unwind(guest_font_tests) add_test(NAME guest_font COMMAND guest_font_tests) add_test_executable(guest_system_font_tests ${LIBS_INCLUDE_DIR}/tests/GuestSystemFont.cpp) target_include_directories(guest_system_font_tests PRIVATE ${LIBS_INCLUDE_DIR}) @@ -24,4 +25,13 @@ if(BUILD_TESTING) set_tests_properties(guest_font_writing_line PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_font_text_tests ${LIBS_INCLUDE_DIR}/tests/GuestFontText.cpp) + target_include_directories(guest_font_text_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_font_text_tests PRIVATE libSceFont libc) + configure_windows_unwind(guest_font_text_tests) + add_test(NAME guest_font_text COMMAND guest_font_text_tests) + if(WIN32) + set_tests_properties(guest_font_text PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() diff --git a/core/libs/prx/libSceFont/include/FontTypes.hpp b/core/libs/prx/libSceFont/include/FontTypes.hpp index f5b1e9b6a9c..de2835651bf 100644 --- a/core/libs/prx/libSceFont/include/FontTypes.hpp +++ b/core/libs/prx/libSceFont/include/FontTypes.hpp @@ -239,6 +239,13 @@ struct FontTextCharacter { std::uint8_t reserved_0x40[136]; }; +struct FontTextCodes { + void* textOrder; + std::uint32_t textCode; + std::uint32_t reserved; + void* systemUse[6]; +}; + struct FontRenderSurface { void* buffer; std::int32_t widthByte; @@ -427,6 +434,8 @@ static_assert(sizeof(FontSurfaceImage) == 0x10); static_assert(sizeof(FontRenderOutput) == 0x40); static_assert(sizeof(FontTextCharacter) == 0xC8); static_assert(offsetof(FontTextCharacter, flags) == 0x38); +static_assert(sizeof(FontTextCodes) == 0x40); +static_assert(offsetof(FontTextCodes, textCode) == 0x08); static_assert(sizeof(FontRenderSurface) == 0x80); static_assert(sizeof(FontStyleFrame) == 0x60); static_assert(offsetof(FontStyleFrame, scalePixelW) == 0x14); diff --git a/core/libs/prx/libSceFont/src/Style.cpp b/core/libs/prx/libSceFont/src/Style.cpp index e5503b8867a..5d1db88f1b8 100644 --- a/core/libs/prx/libSceFont/src/Style.cpp +++ b/core/libs/prx/libSceFont/src/Style.cpp @@ -2,7 +2,10 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include +#include +#include #include "prx/libSceFont/include/FontInternal.hpp" @@ -10,6 +13,48 @@ namespace { using namespace Font; +constexpr std::uint16_t HANDLE_WRITING_VERTICAL = 0x8000; +constexpr int ATTRIBUTE_NONE = 0; +constexpr int ATTRIBUTE_WRITING_HORIZONTAL = 0x40; +constexpr int ATTRIBUTE_WRITING_VERTICAL = 0x41; +constexpr int ATTRIBUTE_RENDER_DETAIL_NO_ADJOIN = 0x10; +constexpr int ATTRIBUTE_RENDER_DETAIL_ADJOIN = 0x11; +constexpr int ATTRIBUTE_VERTICAL_NO_EXCLUSIVE = 0x20; +constexpr int ATTRIBUTE_VERTICAL_EXCLUSIVE = 0x21; +constexpr int ATTRIBUTE_VERTICAL_ROTATION_ENABLE = 0x30; +constexpr int ATTRIBUTE_VERTICAL_ROTATION_DISABLE = 0x31; + +[[noreturn]] void Unsupported(const char* function, const std::string& what) { + throw std::runtime_error(std::string(function) + ": " + what); +} + +std::string AttributeName(int attribute) { + char name[16]; + std::snprintf(name, sizeof(name), "0x%X", static_cast(attribute)); + return name; +} + +void CheckWritingAttribute(const char* function, int attribute) { + switch (attribute) { + case ATTRIBUTE_WRITING_HORIZONTAL: + case ATTRIBUTE_WRITING_VERTICAL: + return; + case ATTRIBUTE_RENDER_DETAIL_NO_ADJOIN: + case ATTRIBUTE_RENDER_DETAIL_ADJOIN: + case ATTRIBUTE_VERTICAL_NO_EXCLUSIVE: + case ATTRIBUTE_VERTICAL_EXCLUSIVE: + case ATTRIBUTE_VERTICAL_ROTATION_ENABLE: + case ATTRIBUTE_VERTICAL_ROTATION_DISABLE: + Unsupported(function, "attribute " + AttributeName(attribute) + " is not modelled"); + default: + Unsupported(function, "unknown attribute " + AttributeName(attribute) + " (error code not verified)"); + } +} + +int WritingAttribute(const FontHandleNative* font) { + return (font->flags & HANDLE_WRITING_VERTICAL) != 0 ? ATTRIBUTE_WRITING_VERTICAL : ATTRIBUTE_WRITING_HORIZONTAL; +} + template int UpdateFontStyle(FontHandle fontHandle, Setter&& setter) { auto* font = GetNativeFont(fontHandle); @@ -145,6 +190,47 @@ int APS5_VABI sceFontGetEffectWeight(FontHandle fontHandle, float* weightXScale, return rc; } +int APS5_VABI sceFontDefineAttribute(FontHandle fontHandle, int attribute, int* oldAttribute) { + auto* font = GetNativeFont(fontHandle); + std::uint32_t fontLock = 0; + if (!font || font->magic != HANDLE_MAGIC) { + if (oldAttribute) *oldAttribute = ATTRIBUTE_NONE; + return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + } + CheckWritingAttribute(__func__, attribute); + if (!AcquireFontLock(font, fontLock)) { + if (oldAttribute) *oldAttribute = ATTRIBUTE_NONE; + return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + } + const int previous = WritingAttribute(font); + if (attribute == ATTRIBUTE_WRITING_VERTICAL) { + font->flags = static_cast(font->flags | HANDLE_WRITING_VERTICAL); + } else { + font->flags = static_cast(font->flags & ~HANDLE_WRITING_VERTICAL); + } + ReleaseFontLock(font, fontLock); + if (oldAttribute) *oldAttribute = previous; + return SCE_FONT_OK; +} + +int APS5_VABI sceFontGetAttribute(FontHandle fontHandle, int attribute, int* nowAttribute) { + if (!nowAttribute) return SCE_FONT_ERROR_INVALID_PARAMETER; + auto* font = GetNativeFont(fontHandle); + std::uint32_t fontLock = 0; + if (!font || font->magic != HANDLE_MAGIC) { + *nowAttribute = ATTRIBUTE_NONE; + return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + } + CheckWritingAttribute(__func__, attribute); + if (!AcquireFontLock(font, fontLock)) { + *nowAttribute = ATTRIBUTE_NONE; + return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + } + *nowAttribute = WritingAttribute(font); + ReleaseFontLock(font, fontLock); + return SCE_FONT_OK; +} + int APS5_VABI sceFontSetupRenderScalePixel(FontHandle fontHandle, float w, float h) { return UpdateRenderStyle(fontHandle, [&](FontHandleNative* font) { if (StyleStateSetScalePixel(&font->cached_style.state, w, h) != 0) font->cached_style.cache_flags_and_direction &= 0xFFFF0000u; diff --git a/core/libs/prx/libSceFont/src/Text.cpp b/core/libs/prx/libSceFont/src/Text.cpp index 5a43ff63fa0..80587e6a7a5 100644 --- a/core/libs/prx/libSceFont/src/Text.cpp +++ b/core/libs/prx/libSceFont/src/Text.cpp @@ -4,8 +4,12 @@ #include #include #include +#include #include +#include #include +#include +#include #include #include "prx/libSceFont/include/FontInternal.hpp" @@ -27,6 +31,56 @@ constexpr std::size_t MAX_PARSED_CHARACTERS = 4096; constexpr std::int32_t WRITING_MASK_NONE = 0; constexpr std::int32_t WRITING_MASK_FORMAT_CHARACTERS = 1; constexpr std::uint64_t CHARACTER_FLAG_FORMAT = 1ull << 42; +constexpr std::uint64_t TEXT_CODES_TAG = 0x5345444F43545854ull; +constexpr int SYLLABLE_STRING_NON_DETECTION = 0; + +struct TextCodesState { + std::uint64_t tag; + FontTextCharacter* current; + const FontTextCharacter* term; +}; + +static_assert(sizeof(TextCodesState) <= sizeof(FontTextCodes::systemUse)); + +[[noreturn]] void Unsupported(const char* function, const std::string& what) { + throw std::runtime_error(std::string(function) + ": " + what); +} + +std::string CodeName(std::uint32_t code) { + char name[16]; + std::snprintf(name, sizeof(name), "U+%04X", static_cast(code)); + return name; +} + +FontTextCodes* SetTextCodesStep(FontTextCodes* textCodes, FontTextCharacter* character, const FontTextCharacter* term) { + textCodes->textOrder = character->textOrder; + textCodes->textCode = character->characterCode; + const TextCodesState state{TEXT_CODES_TAG, character, term}; + std::memcpy(textCodes->systemUse, &state, sizeof(state)); + return textCodes; +} + +TextCodesState GetTextCodesStep(const char* function, const FontTextCodes* textCodesStep) { + TextCodesState state{}; + std::memcpy(&state, textCodesStep->systemUse, sizeof(state)); + if (state.tag != TEXT_CODES_TAG || !state.current) Unsupported(function, "text codes step not set up by sceFontCharactersRefersTextCodes"); + return state; +} + +bool IsSyllableFreeCode(std::uint32_t code) { + struct Range { + std::uint32_t first; + std::uint32_t last; + }; + static constexpr Range ranges[] = { + {0x0000, 0x00AC}, {0x00AE, 0x02FF}, {0x0370, 0x03FF}, {0x0400, 0x0482}, {0x048A, 0x052F}, + {0x1E00, 0x1FFF}, {0x2000, 0x200A}, {0x2010, 0x2027}, {0x202F, 0x205F}, {0x2070, 0x20CF}, + {0x2100, 0x2BFF}, {0x3000, 0x3029}, {0x3030, 0x303F}, {0x3041, 0x3096}, {0x309B, 0x30FF}, + {0x31F0, 0x31FF}, {0x3400, 0x4DBF}, {0x4E00, 0x9FFF}, {0xF900, 0xFAFF}, {0xFF01, 0xFF9D}, + {0xFFE0, 0xFFEE}, {0x20000, 0x3FFFF}, + }; + return std::any_of(std::begin(ranges), std::end(ranges), [code](const Range& range) { return code >= range.first && code <= range.last; }); +} struct CharacterStorage { std::atomic refCount{1}; @@ -539,6 +593,39 @@ FontTextCharacter* APS5_VABI sceFontCharacterRefersTextNext(const FontTextCharac return nullptr; } +int APS5_VABI sceFontCharacterGetSyllableStringState(const FontTextCharacter* textCharacter, int* syllableStringState) { + if (!textCharacter || !syllableStringState) return SCE_FONT_ERROR_INVALID_PARAMETER; + const FontTextCharacter* const neighbourhood[] = {textCharacter->prev, textCharacter, textCharacter->next}; + for (const FontTextCharacter* character : neighbourhood) { + if (character && !IsSyllableFreeCode(character->characterCode)) Unsupported(__func__, "syllable detection next to " + CodeName(character->characterCode) + " is not modelled"); + } + *syllableStringState = SYLLABLE_STRING_NON_DETECTION; + return SCE_FONT_OK; +} + +FontTextCodes* APS5_VABI sceFontCharactersRefersTextCodes(const FontTextCharacter* textCharacter, const FontTextCharacter* termCharacter, FontTextCodes* textCodes) { + if (!textCodes || !textCharacter) return nullptr; + if (textCharacter == termCharacter) Unsupported(__func__, "a start character equal to the term character is not modelled"); + std::memset(textCodes, 0, sizeof(*textCodes)); + return SetTextCodesStep(textCodes, const_cast(textCharacter), termCharacter); +} + +FontTextCodes* APS5_VABI sceFontTextCodesStepNext(FontTextCodes* textCodesStep) { + if (!textCodesStep) return nullptr; + const TextCodesState state = GetTextCodesStep(__func__, textCodesStep); + FontTextCharacter* next = state.current->next; + if (!next || next == state.term) return nullptr; + return SetTextCodesStep(textCodesStep, next, state.term); +} + +FontTextCodes* APS5_VABI sceFontTextCodesStepBack(FontTextCodes* textCodesStep) { + if (!textCodesStep) return nullptr; + const TextCodesState state = GetTextCodesStep(__func__, textCodesStep); + FontTextCharacter* previous = state.current->prev; + if (!previous) return nullptr; + return SetTextCodesStep(textCodesStep, previous, state.term); +} + int APS5_VABI sceFontWritingInit(FontWriting* fontWriting, FontString fontString, const FontTextCharacter* fontCharacter) { if (!fontWriting || !fontString || !fontCharacter) return SCE_FONT_ERROR_INVALID_PARAMETER; auto* data = GetStringData(fontString); diff --git a/core/libs/prx/libSceFont/src/Unimplemented.cpp b/core/libs/prx/libSceFont/src/Unimplemented.cpp index 87c3bbcd5e7..5b7b1b61e48 100644 --- a/core/libs/prx/libSceFont/src/Unimplemented.cpp +++ b/core/libs/prx/libSceFont/src/Unimplemented.cpp @@ -7,16 +7,6 @@ extern "C" { -int APS5_VABI sceFontCharacterGetSyllableStringState() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontCharactersRefersTextCodes() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceFontControl() { NotImplemented_nid_no_patch(__func__); return 0; @@ -42,11 +32,6 @@ int APS5_VABI sceFontCreateWords() { return 0; } -int APS5_VABI sceFontDefineAttribute() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceFontDestroyGraphicsDevice() { NotImplemented_nid_no_patch(__func__); return 0; @@ -63,11 +48,6 @@ int APS5_VABI sceFontDestroyWords() { } -int APS5_VABI sceFontGetAttribute() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceFontGetFontGlyphsOutlineProfile() { NotImplemented_nid_no_patch(__func__); return 0; @@ -383,16 +363,6 @@ int APS5_VABI sceFontSupportGlyphs() { return 0; } -int APS5_VABI sceFontTextCodesStepBack() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontTextCodesStepNext() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceFontWordsFindWordCharacters() { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/tests/GuestFont.cpp b/core/libs/tests/GuestFont.cpp index 97e814da71c..34a9bd205a7 100644 --- a/core/libs/tests/GuestFont.cpp +++ b/core/libs/tests/GuestFont.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -39,6 +40,11 @@ int APS5_VABI sceFontGetRenderScaledKerning(FontHandle, std::uint32_t, std::uint int APS5_VABI sceFontGenerateCharGlyph(FontHandle, std::uint32_t, const FontGenerateGlyphDetail*, FontGlyph*); int APS5_VABI sceFontGlyphDefineAttribute(FontGlyph, std::uint32_t, std::uint64_t); int APS5_VABI sceFontDeleteGlyph(const FontMemory*, FontGlyph*); +int APS5_VABI sceFontDefineAttribute(FontHandle, int, int*); +int APS5_VABI sceFontGetAttribute(FontHandle, int, int*); +int APS5_VABI sceFontOpenFontInstance(FontHandle, FontHandle, FontHandle*); +void APS5_VABI sceFontRenderSurfaceInit(FontRenderSurface*, void*, int, int, int, int); +int APS5_VABI sceFontRenderCharGlyphImage(FontHandle, std::uint32_t, FontRenderSurface*, float, float, FontGlyphMetrics*, FontRenderOutput*); const void* APS5_VABI sceFontSelectLibraryFt(int); const void* APS5_VABI sceFontSelectRendererFt(int); } @@ -51,6 +57,16 @@ static void Check(bool value, int line) { } #define Require(value) Check((value), __LINE__) +template +static bool Throws(F f) { + try { + f(); + } catch (const std::exception&) { + return true; + } + return false; +} + static int allocations = 0; static void* APS5_VABI Allocate(void*, std::uint32_t size) { ++allocations; @@ -76,9 +92,9 @@ static std::vector Words(std::initializer_list values) { return out; } -static std::vector BuildFont(int glyphCount, std::map> tables) { +static std::vector BuildFont(int glyphCount, std::map> tables, int yMax = 0) { tables["glyf"] = Words({0, 0}); - tables["head"] = Words({1, 0, 1, 0, 0, 0, 0x5F0F, 0x3CF5, 0, 1000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 2, 0, 0}); + tables["head"] = Words({1, 0, 1, 0, 0, 0, 0x5F0F, 0x3CF5, 0, 1000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, yMax, 0, 8, 2, 0, 0}); tables["hhea"] = Words({1, 0, 800, -200, 0, 500, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1}); tables["hmtx"] = Words({500}); tables["hmtx"].resize(2 + 2 * static_cast(glyphCount)); @@ -109,7 +125,7 @@ static std::vector KerningFont() { return BuildFont(3, { {"cmap", Words({0, 1, 3, 1, 0, 12, 4, 40, 0, 6, 4, 1, 2, 'A', 'V', 0xFFFF, 0, 'A', 'V', 0xFFFF, 1 - 'A', 2 - 'V', 1, 0, 0, 0})}, {"kern", Words({0, 1, 0, 20, 1, 1, 6, 0, 0, 1, 2, -200})}, - }); + }, 800); } static bool KerningIs(const FontKerning& kerning, float offsetX) { @@ -276,6 +292,46 @@ int main() { Require(sceFontGetRenderScaledKerning(nullptr, 'A', 'V', &kerning) == SCE_FONT_ERROR_INVALID_FONT_HANDLE && KerningIs(kerning, 0.0f)); kerning = {1.0f, 1.0f, 1.0f, 1.0f}; Require(sceFontGetRenderScaledKerning(&unopened, 'A', 'V', &kerning) == SCE_FONT_ERROR_INVALID_FONT_HANDLE && KerningIs(kerning, 0.0f)); + int attribute = 1; + Require(sceFontGetAttribute(font, 0x40, &attribute) == SCE_FONT_OK && attribute == 0x40); + std::vector pixels(64 * 64); + FontRenderSurface surface{}; + sceFontRenderSurfaceInit(&surface, pixels.data(), 64, 1, 64, 64); + FontGlyphMetrics glyphMetrics{}; + FontRenderOutput horizontal{}; + Require(sceFontRenderCharGlyphImage(font, 'A', &surface, 10.0f, 10.0f, &glyphMetrics, &horizontal) == SCE_FONT_OK); + Require(horizontal.UpdateRect.x == 10 && horizontal.UpdateRect.y == 42); + Require(sceFontDefineAttribute(font, 0x41, &attribute) == SCE_FONT_OK && attribute == 0x40); + Require(sceFontGetAttribute(font, 0x40, &attribute) == SCE_FONT_OK && attribute == 0x41); + Require(sceFontGetAttribute(font, 0x41, &attribute) == SCE_FONT_OK && attribute == 0x41); + FontRenderOutput vertical{}; + Require(sceFontRenderCharGlyphImage(font, 'A', &surface, 10.0f, 10.0f, &glyphMetrics, &vertical) == SCE_FONT_OK); + Require(vertical.UpdateRect.x == 10 && vertical.UpdateRect.y == 10); + FontHandle instance = nullptr; + Require(sceFontOpenFontInstance(font, nullptr, &instance) == SCE_FONT_OK && instance != nullptr); + Require(sceFontGetAttribute(instance, 0x40, &attribute) == SCE_FONT_OK && attribute == 0x41); + Require(sceFontDefineAttribute(instance, 0x40, nullptr) == SCE_FONT_OK); + Require(sceFontGetAttribute(instance, 0x40, &attribute) == SCE_FONT_OK && attribute == 0x40); + Require(sceFontGetAttribute(font, 0x40, &attribute) == SCE_FONT_OK && attribute == 0x41); + Require(sceFontCloseFont(instance) == SCE_FONT_OK); + Require(sceFontDefineAttribute(font, 0x41, nullptr) == SCE_FONT_OK); + Require(sceFontDefineAttribute(font, 0x40, &attribute) == SCE_FONT_OK && attribute == 0x41); + Require(sceFontGetAttribute(font, 0x40, &attribute) == SCE_FONT_OK && attribute == 0x40); + Require(sceFontGetAttribute(font, 0x40, nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER); + attribute = 1; + Require(sceFontGetAttribute(nullptr, 0x40, &attribute) == SCE_FONT_ERROR_INVALID_FONT_HANDLE && attribute == 0); + attribute = 1; + Require(sceFontGetAttribute(&unopened, 0x40, &attribute) == SCE_FONT_ERROR_INVALID_FONT_HANDLE && attribute == 0); + attribute = 1; + Require(sceFontDefineAttribute(nullptr, 0x41, &attribute) == SCE_FONT_ERROR_INVALID_FONT_HANDLE && attribute == 0); + Require(sceFontDefineAttribute(&unopened, 0x41, nullptr) == SCE_FONT_ERROR_INVALID_FONT_HANDLE); + attribute = 1; + Require(Throws([&] { sceFontDefineAttribute(font, 0x11, &attribute); }) && attribute == 1); + Require(Throws([&] { sceFontDefineAttribute(font, 0x30, nullptr); })); + Require(Throws([&] { sceFontGetAttribute(font, 0x20, &attribute); }) && attribute == 1); + Require(Throws([&] { sceFontDefineAttribute(font, 0x42, nullptr); })); + Require(Throws([&] { sceFontGetAttribute(font, 0, &attribute); })); + Require(sceFontGetAttribute(font, 0x41, &attribute) == SCE_FONT_OK && attribute == 0x40); Require(sceFontUnbindRenderer(font) == SCE_FONT_OK); Require(sceFontGetRenderScaledKerning(font, 'A', 'V', &kerning) == SCE_FONT_ERROR_NOT_BOUND_RENDERER); Require(sceFontCloseFont(font) == SCE_FONT_OK); diff --git a/core/libs/tests/GuestFontText.cpp b/core/libs/tests/GuestFontText.cpp new file mode 100644 index 00000000000..2d51a57484a --- /dev/null +++ b/core/libs/tests/GuestFontText.cpp @@ -0,0 +1,144 @@ +#include "prx/libSceFont/include/FontTypes.hpp" +#include +#include +#include +#include +#include + +extern "C" { +int APS5_VABI sceFontMemoryInit(FontMemory*, void*, std::uint32_t, const FontMemoryInterface*, void*, FontMemoryDestroyFunction, void*); +int APS5_VABI sceFontMemoryTerm(FontMemory*); +int APS5_VABI sceFontTextSourceInit(FontTextSource*, const void*, std::uint32_t, FontTextParseFunction, void*); +int APS5_VABI sceFontTextSourceSetDefaultFont(FontTextSource*, FontHandle); +int APS5_VABI sceFontCreateString(const FontMemory*, FontTextSource*, const FontCreateStringDetail*, FontString*); +int APS5_VABI sceFontDestroyString(FontString*); +FontTextCharacter* APS5_VABI sceFontStringRefersTextCharacters(FontString, std::uint32_t*); +FontTextCodes* APS5_VABI sceFontCharactersRefersTextCodes(const FontTextCharacter*, const FontTextCharacter*, FontTextCodes*); +FontTextCodes* APS5_VABI sceFontTextCodesStepNext(FontTextCodes*); +FontTextCodes* APS5_VABI sceFontTextCodesStepBack(FontTextCodes*); +int APS5_VABI sceFontCharacterGetSyllableStringState(const FontTextCharacter*, int*); +} + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "Font text check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +template +static bool Throws(F f) { + try { + f(); + } catch (const std::exception&) { + return true; + } + return false; +} + +static int allocations = 0; +static void* APS5_VABI Allocate(void*, std::uint32_t size) { + ++allocations; + return std::malloc(size); +} +static void APS5_VABI Release(void*, void* pointer) { + if (pointer) --allocations; + std::free(pointer); +} + +static std::int32_t APS5_VABI ParseCodes(FontTextSource* source, void** order, FontTextParseResult* result) { + const auto* current = static_cast(source->current); + *order = const_cast(current); + if (current == source->end) { + result->Terminate.terminateCode = 0; + return 0; + } + result->FontCode.font = nullptr; + result->FontCode.code = *current; + source->current = current + 1; + return 1; +} + +struct Text { + FontString string = nullptr; + FontTextCharacter* characters = nullptr; + std::uint32_t count = 0; +}; + +static Text CreateText(const FontMemory& memory, const std::vector& codes, FontHandle font) { + FontTextSource source{}; + Require(sceFontTextSourceInit(&source, codes.data(), static_cast(codes.size() * sizeof(std::uint32_t)), ParseCodes, nullptr) == SCE_FONT_OK); + Require(sceFontTextSourceSetDefaultFont(&source, font) == SCE_FONT_OK); + Text text; + Require(sceFontCreateString(&memory, &source, nullptr, &text.string) == SCE_FONT_OK && text.string != nullptr); + text.characters = sceFontStringRefersTextCharacters(text.string, &text.count); + Require(text.characters != nullptr && text.count == codes.size()); + return text; +} + +static bool StepIs(const FontTextCodes* step, const FontTextCodes* codes, std::uint32_t code, const std::uint32_t* order) { + return step == codes && step->textCode == code && step->textOrder == order; +} + +static bool StateIs(const FontTextCharacter* character, int expected) { + int state = -1; + return sceFontCharacterGetSyllableStringState(character, &state) == SCE_FONT_OK && state == expected; +} + +int main() { + const FontMemoryInterface iface{Allocate, Release, nullptr, nullptr, nullptr, nullptr}; + FontMemory memory{}; + Require(sceFontMemoryInit(&memory, nullptr, 0, &iface, nullptr, nullptr, nullptr) == SCE_FONT_OK); + FontHandleOpaque fontStorage{}; + const FontHandle font = &fontStorage; + + const std::vector latin{'A', 'b', ' ', 0x00E9, 0x4E00}; + Text text = CreateText(memory, latin, font); + FontTextCharacter* characters = text.characters; + + FontTextCodes codes{}; + Require(StepIs(sceFontCharactersRefersTextCodes(&characters[0], &characters[3], &codes), &codes, 'A', &latin[0])); + Require(StepIs(sceFontTextCodesStepNext(&codes), &codes, 'b', &latin[1])); + Require(StepIs(sceFontTextCodesStepNext(&codes), &codes, ' ', &latin[2])); + Require(sceFontTextCodesStepNext(&codes) == nullptr && codes.textCode == ' '); + Require(StepIs(sceFontTextCodesStepBack(&codes), &codes, 'b', &latin[1])); + Require(StepIs(sceFontTextCodesStepBack(&codes), &codes, 'A', &latin[0])); + Require(sceFontTextCodesStepBack(&codes) == nullptr && codes.textCode == 'A'); + Require(StepIs(sceFontTextCodesStepNext(&codes), &codes, 'b', &latin[1])); + + Require(StepIs(sceFontCharactersRefersTextCodes(&characters[3], nullptr, &codes), &codes, 0x00E9, &latin[3])); + Require(StepIs(sceFontTextCodesStepNext(&codes), &codes, 0x4E00, &latin[4])); + Require(sceFontTextCodesStepNext(&codes) == nullptr); + Require(StepIs(sceFontTextCodesStepBack(&codes), &codes, 0x00E9, &latin[3])); + + Require(Throws([&] { sceFontCharactersRefersTextCodes(&characters[1], &characters[1], &codes); }) && StepIs(&codes, &codes, 0x00E9, &latin[3])); + Require(sceFontCharactersRefersTextCodes(nullptr, &characters[1], &codes) == nullptr && StepIs(&codes, &codes, 0x00E9, &latin[3])); + Require(sceFontCharactersRefersTextCodes(&characters[0], nullptr, nullptr) == nullptr); + Require(sceFontTextCodesStepNext(nullptr) == nullptr); + Require(sceFontTextCodesStepBack(nullptr) == nullptr); + FontTextCodes unset{}; + Require(Throws([&] { sceFontTextCodesStepNext(&unset); })); + Require(Throws([&] { sceFontTextCodesStepBack(&unset); })); + + for (std::uint32_t i = 0; i < text.count; ++i) Require(StateIs(&characters[i], 0)); + int state = 7; + Require(sceFontCharacterGetSyllableStringState(nullptr, &state) == SCE_FONT_ERROR_INVALID_PARAMETER && state == 7); + Require(sceFontCharacterGetSyllableStringState(&characters[0], nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER); + Require(sceFontDestroyString(&text.string) == SCE_FONT_OK && text.string == nullptr); + + const std::vector mixed{'x', 'e', 0x0301, 'y', 'z', 0x0E01, 0x0E35}; + Text marks = CreateText(memory, mixed, font); + Require(StateIs(&marks.characters[0], 0)); + Require(Throws([&] { sceFontCharacterGetSyllableStringState(&marks.characters[1], &state); }) && state == 7); + Require(Throws([&] { sceFontCharacterGetSyllableStringState(&marks.characters[2], &state); })); + Require(Throws([&] { sceFontCharacterGetSyllableStringState(&marks.characters[3], &state); })); + Require(Throws([&] { sceFontCharacterGetSyllableStringState(&marks.characters[4], &state); })); + Require(Throws([&] { sceFontCharacterGetSyllableStringState(&marks.characters[5], &state); })); + Require(Throws([&] { sceFontCharacterGetSyllableStringState(&marks.characters[6], &state); })); + Require(sceFontDestroyString(&marks.string) == SCE_FONT_OK); + + Require(allocations == 0); + Require(sceFontMemoryTerm(&memory) == SCE_FONT_OK); + std::puts("font text: ok"); +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index a7b2e6907d1..39fe360b174 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -97,6 +97,12 @@ Throughout the project, every function at every stage either **does exactly what - [sceFontGetFontResolution](../../core/libs/prx/libSceFont/src/Open.cpp) (libSceFont) - signature taken from the IDA type database of [Orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json), not confirmed on a title; the outputs are inferred from the names. The resolution is the face's units per EM and the scale pixel the driver's face scale (units per EM / 64 for FreeType), the pixel scale a font has when opened. Either output may be null, as in `sceFontGetResolutionDpi` - [sceFontCreateWritingLine, sceFontWritingLineWritesOrder](../../core/libs/prx/libSceFont/src/WritingLine.cpp) (libSceFont) - signatures and the `SceFontWritingLineStep`/`SceFontCreateWritingLineDetail` layouts (detail id `0x0FD5`) taken from [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Font/SceFontStructs.cs), not confirmed on a title; the meaning of the 64-bit writing attribute is unknown - [sceFontWritingLineGetOrderingSpace](../../core/libs/prx/libSceFont/src/WritingLine.cpp) (libSceFont) - signature taken from the IDA type database of [Orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json) and the [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Font/SceFont.Engine.cs) bindings, not confirmed on a title. The modelled line applies no spacing, so the head, inline, tail and advance spaces are 0; what spacing the console applies is unknown. A null output throws +- [sceFontDefineAttribute, sceFontGetAttribute](../../core/libs/prx/libSceFont/src/Style.cpp) (libSceFont) - signatures taken from the IDA type database of [Orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json) and the attribute values from [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Font/SceFontConstants.cs), not confirmed on a title. Only the writing attribute is modelled: `0x41` (vertical) sets and `0x40` (horizontal) clears bit 15 of the handle flags, which the `sceFontRenderCharGlyphImage` decompiled by [shadPS4](https://github.com/shadps4-emu/shadPS4) reads to choose its vertical path; that this flag is the writing attribute is inferred from SharpProspero, which says the attribute chooses that call's writing mode. A newly opened handle is horizontal, an instance inherits the flag, and `sceFontGetAttribute` with either writing value reads the current one. `oldAttribute` may be null, as in [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/main/src/libs/libFont.cpp). An invalid handle writes 0 (SharpProspero's `None`, reported on error) and returns `INVALID_FONT_HANDLE`; a null `nowAttribute` returns `INVALID_PARAMETER`, as in `sceFontGetLibrary`. The render detail (`0x10`/`0x11`), vertical exclusive (`0x20`/`0x21`) and vertical rotation (`0x30`/`0x31`) attributes, whose defaults and effects are unknown, and other values throw +- [sceFontCharactersRefersTextCodes, sceFontTextCodesStepNext, sceFontTextCodesStepBack](../../core/libs/prx/libSceFont/src/Text.cpp) (libSceFont) - signatures taken from the IDA type database of [Orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json), the 64-byte `SceFontTextCodes` layout (text order at 0, code at 8) from [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Font/SceFontStructs.cs) and the stepping from [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/main/src/libs/libFont.cpp), not confirmed on a title. The text codes refer to one character's text order and code. Stepping next stops before the term character or at the end of the string, and stepping back stops at the start of the string; both then return null and leave the step unchanged. A null start or null text codes returns null and writes nothing (KytyPS5 returns the unfilled text codes). A start equal to the term character, whose result is unknown, throws, as does stepping text codes that `sceFontCharactersRefersTextCodes` did not set up +- [sceFontCharacterGetSyllableStringState](../../core/libs/prx/libSceFont/src/Text.cpp) (libSceFont) - signature taken from the IDA type database of [Orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json) and the states (0 non-detection to 4 connecting, described as syllable-cluster states in complex scripts) from [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Font/SceFontConstants.cs), not confirmed on a title. The state is 0 when the character and its neighbours are Latin, Greek or Cyrillic letters, spaces, punctuation, symbols, kana or CJK ideographs, which cannot be part of a syllable cluster; [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/main/src/libs/libFont.cpp) reports 0 for every character. Next to any other code (combining marks, format characters and joiners, Hangul, Thai and other complex scripts) it throws, since no reference gives the clustering rules. The `detections` of `sceFontCreateString` are not taken into account; which bit requests syllable detection is unknown. A null character or output returns `INVALID_PARAMETER`, as in `sceFontCharacterGetBidiLevel` +- [sceFont{Create,Destroy}GraphicsDevice, sceFont{Create,Destroy}GraphicsService, sceFontCreateGraphicsServiceWithEdition, sceFontGraphics{BeginFrame,EndFrame,SetFramePolicy,DrawingCancel,DrawingFinish,ExchangeResource,RenderResource,Release,GetDeviceUsage}, sceFontGraphicsRegionInit{,Circular,Roundish}, sceFontGraphicsStructure{Canvas,CanvasSequence,Design,DesignResource,SurfaceTexture}](../../core/libs/prx/libSceFont/src/Unimplemented.cpp) (libSceFont) - signatures unknown; declared without parameters and not implemented. [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/font/font.cpp) declares them without parameters and returns success without doing anything; Orbital, SharpProspero, OpenOrbis, KytyPS5 and prosper do not declare them +- [sceFontGraphicsFill{MethodInit,PlotInit,PlotSetLayout,PlotSetMapping,RatesInit,RatesSetFillEffect,RatesSetLayout,RatesSetMapping}, sceFontGraphicsSetup{Clipping,ColorRates,FillMethod,FillRates,GlyphFill,GlyphFillPlot,HandleDefault,Location,Positioning,Rotation,Scaling,ShapeFill,ShapeFillPlot}, sceFontGraphicsUpdate{Clipping,ColorRates,FillMethod,FillRates,GlyphFill,GlyphFillPlot,Location,Positioning,Rotation,Scaling,ShapeFill,ShapeFillPlot}](../../core/libs/prx/libSceFont/src/Unimplemented.cpp) (libSceFont) - unknown signatures and structure layouts; declared without parameters. [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/font/font.cpp) has parameterless placeholders that return success, and [fpPS4](https://github.com/red-prig/fpPS4) lists only their names +- [sceFontFtInitAliases, sceFontFtSetAliasFont, sceFontFtSetAliasPath, sceFontFtTermAliases, sceFontFtSupport{Bdf,Cid,FontFormats,OpenType,OpenTypeOtf,OpenTypeTtf,Pcf,Pfr,SystemFonts,TrueType,TrueTypeGx,Type1,Type42,WinFonts}, sceFontSelectGlyphsFt](../../core/libs/prx/libSceFontFt/Export.cpp) (libSceFontFt) - signatures unknown; declared without parameters. [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/font/fontft.cpp) declares them without parameters and returns success without doing anything; no other reference gives their parameters, return values or behaviour - [sceSaveDataTransferringMountPs4, sceSaveDataDirNameSearchPs4](../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - arguments assumed to be those of `sceSaveDataTransferringMount` and `sceSaveDataDirNameSearch`: KytyPS5 binds both NIDs to the PS5 functions, and [prosper](https://github.com/mattias800/prosper) captured the same result layout on a title for the search. There is no PS4 save area, so the mount returns `NOT_FOUND` and the search reports zero hits - [sceCoredumpWriteUserString](../../core/libs/prx/libkernel/System/src/Coredump.cpp) and [sceSaveDataConvert, sceSaveDataGetConvertProgress](../../core/libs/prx/libSceSaveData.native/Export.cpp) - imported by PPSA16064, not called during its start-up and added without an implementation; unknown signatures - [sce::Json::Parser::parse](../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - a number that overflows a double (e.g. `1e309`) throws: whether the console rejects the document or stores an infinite real is unknown From 57bd47c50fac728316db2f173f15ac2d3ea73c67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sat, 10 Oct 2026 01:16:31 +0200 Subject: [PATCH 2/2] docs(TechnicalDebt): drop entries for font stubs this change does not touch --- docs/dev/TechnicalDebt.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 39fe360b174..1ae4ae4f894 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -100,9 +100,6 @@ Throughout the project, every function at every stage either **does exactly what - [sceFontDefineAttribute, sceFontGetAttribute](../../core/libs/prx/libSceFont/src/Style.cpp) (libSceFont) - signatures taken from the IDA type database of [Orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json) and the attribute values from [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Font/SceFontConstants.cs), not confirmed on a title. Only the writing attribute is modelled: `0x41` (vertical) sets and `0x40` (horizontal) clears bit 15 of the handle flags, which the `sceFontRenderCharGlyphImage` decompiled by [shadPS4](https://github.com/shadps4-emu/shadPS4) reads to choose its vertical path; that this flag is the writing attribute is inferred from SharpProspero, which says the attribute chooses that call's writing mode. A newly opened handle is horizontal, an instance inherits the flag, and `sceFontGetAttribute` with either writing value reads the current one. `oldAttribute` may be null, as in [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/main/src/libs/libFont.cpp). An invalid handle writes 0 (SharpProspero's `None`, reported on error) and returns `INVALID_FONT_HANDLE`; a null `nowAttribute` returns `INVALID_PARAMETER`, as in `sceFontGetLibrary`. The render detail (`0x10`/`0x11`), vertical exclusive (`0x20`/`0x21`) and vertical rotation (`0x30`/`0x31`) attributes, whose defaults and effects are unknown, and other values throw - [sceFontCharactersRefersTextCodes, sceFontTextCodesStepNext, sceFontTextCodesStepBack](../../core/libs/prx/libSceFont/src/Text.cpp) (libSceFont) - signatures taken from the IDA type database of [Orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json), the 64-byte `SceFontTextCodes` layout (text order at 0, code at 8) from [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Font/SceFontStructs.cs) and the stepping from [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/main/src/libs/libFont.cpp), not confirmed on a title. The text codes refer to one character's text order and code. Stepping next stops before the term character or at the end of the string, and stepping back stops at the start of the string; both then return null and leave the step unchanged. A null start or null text codes returns null and writes nothing (KytyPS5 returns the unfilled text codes). A start equal to the term character, whose result is unknown, throws, as does stepping text codes that `sceFontCharactersRefersTextCodes` did not set up - [sceFontCharacterGetSyllableStringState](../../core/libs/prx/libSceFont/src/Text.cpp) (libSceFont) - signature taken from the IDA type database of [Orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json) and the states (0 non-detection to 4 connecting, described as syllable-cluster states in complex scripts) from [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Font/SceFontConstants.cs), not confirmed on a title. The state is 0 when the character and its neighbours are Latin, Greek or Cyrillic letters, spaces, punctuation, symbols, kana or CJK ideographs, which cannot be part of a syllable cluster; [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/main/src/libs/libFont.cpp) reports 0 for every character. Next to any other code (combining marks, format characters and joiners, Hangul, Thai and other complex scripts) it throws, since no reference gives the clustering rules. The `detections` of `sceFontCreateString` are not taken into account; which bit requests syllable detection is unknown. A null character or output returns `INVALID_PARAMETER`, as in `sceFontCharacterGetBidiLevel` -- [sceFont{Create,Destroy}GraphicsDevice, sceFont{Create,Destroy}GraphicsService, sceFontCreateGraphicsServiceWithEdition, sceFontGraphics{BeginFrame,EndFrame,SetFramePolicy,DrawingCancel,DrawingFinish,ExchangeResource,RenderResource,Release,GetDeviceUsage}, sceFontGraphicsRegionInit{,Circular,Roundish}, sceFontGraphicsStructure{Canvas,CanvasSequence,Design,DesignResource,SurfaceTexture}](../../core/libs/prx/libSceFont/src/Unimplemented.cpp) (libSceFont) - signatures unknown; declared without parameters and not implemented. [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/font/font.cpp) declares them without parameters and returns success without doing anything; Orbital, SharpProspero, OpenOrbis, KytyPS5 and prosper do not declare them -- [sceFontGraphicsFill{MethodInit,PlotInit,PlotSetLayout,PlotSetMapping,RatesInit,RatesSetFillEffect,RatesSetLayout,RatesSetMapping}, sceFontGraphicsSetup{Clipping,ColorRates,FillMethod,FillRates,GlyphFill,GlyphFillPlot,HandleDefault,Location,Positioning,Rotation,Scaling,ShapeFill,ShapeFillPlot}, sceFontGraphicsUpdate{Clipping,ColorRates,FillMethod,FillRates,GlyphFill,GlyphFillPlot,Location,Positioning,Rotation,Scaling,ShapeFill,ShapeFillPlot}](../../core/libs/prx/libSceFont/src/Unimplemented.cpp) (libSceFont) - unknown signatures and structure layouts; declared without parameters. [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/font/font.cpp) has parameterless placeholders that return success, and [fpPS4](https://github.com/red-prig/fpPS4) lists only their names -- [sceFontFtInitAliases, sceFontFtSetAliasFont, sceFontFtSetAliasPath, sceFontFtTermAliases, sceFontFtSupport{Bdf,Cid,FontFormats,OpenType,OpenTypeOtf,OpenTypeTtf,Pcf,Pfr,SystemFonts,TrueType,TrueTypeGx,Type1,Type42,WinFonts}, sceFontSelectGlyphsFt](../../core/libs/prx/libSceFontFt/Export.cpp) (libSceFontFt) - signatures unknown; declared without parameters. [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/font/fontft.cpp) declares them without parameters and returns success without doing anything; no other reference gives their parameters, return values or behaviour - [sceSaveDataTransferringMountPs4, sceSaveDataDirNameSearchPs4](../../core/libs/prx/libSceSaveData.native/Export.cpp) (libSceSaveData.native) - arguments assumed to be those of `sceSaveDataTransferringMount` and `sceSaveDataDirNameSearch`: KytyPS5 binds both NIDs to the PS5 functions, and [prosper](https://github.com/mattias800/prosper) captured the same result layout on a title for the search. There is no PS4 save area, so the mount returns `NOT_FOUND` and the search reports zero hits - [sceCoredumpWriteUserString](../../core/libs/prx/libkernel/System/src/Coredump.cpp) and [sceSaveDataConvert, sceSaveDataGetConvertProgress](../../core/libs/prx/libSceSaveData.native/Export.cpp) - imported by PPSA16064, not called during its start-up and added without an implementation; unknown signatures - [sce::Json::Parser::parse](../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - a number that overflows a double (e.g. `1e309`) throws: whether the console rejects the document or stores an infinite real is unknown