From b50ed5995315c2d84114c2775f13d106cff1d1b3 Mon Sep 17 00:00:00 2001 From: Ondra97 <47064087+Ondra97@users.noreply.github.com> Date: Wed, 21 Oct 2026 23:45:04 +0200 Subject: [PATCH 1/4] fix(libSceFreeType): preserve referenced face lifetimes --- core/libs/prx/libSceFreeType/include/Api.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/core/libs/prx/libSceFreeType/include/Api.hpp b/core/libs/prx/libSceFreeType/include/Api.hpp index edf90a2637..0c08f7b10f 100644 --- a/core/libs/prx/libSceFreeType/include/Api.hpp +++ b/core/libs/prx/libSceFreeType/include/Api.hpp @@ -12,6 +12,7 @@ int APS5_VABI FT_New_Face_nid_postfix(FreeType::GuestLibrary* library, const cha int APS5_VABI FT_New_Memory_Face_nid_postfix(FreeType::GuestLibrary* library, const unsigned char* fileBase, std::int64_t fileSize, std::int64_t faceIndex, FreeType::GuestFaceRec** aface); int APS5_VABI FT_Reference_Face_nid_postfix(FreeType::GuestFaceRec* face); int APS5_VABI FT_Done_Face_nid_postfix(FreeType::GuestFaceRec* face); +int APS5_VABI FT_Reference_Face_nid_postfix(FreeType::GuestFaceRec* face); int APS5_VABI FT_Select_Size_nid_postfix(FreeType::GuestFaceRec* face, std::int32_t strikeIndex); int APS5_VABI FT_Set_Pixel_Sizes_nid_postfix(FreeType::GuestFaceRec* face, std::uint32_t pixelWidth, std::uint32_t pixelHeight); int APS5_VABI FT_Set_Char_Size_nid_postfix(FreeType::GuestFaceRec* face, std::int64_t charWidth, std::int64_t charHeight, std::uint32_t horizontalResolution, std::uint32_t verticalResolution); From a476e421882377b61be8d9ae972c30c218e74804 Mon Sep 17 00:00:00 2001 From: Ondra97 <47064087+Ondra97@users.noreply.github.com> Date: Thu, 22 Oct 2026 03:51:33 +0200 Subject: [PATCH 2/4] feat(libSceFreeType): manage multiple size objects --- core/libs/prx/libSceFreeType/Export.cpp | 66 +++++- core/libs/prx/libSceFreeType/include/Api.hpp | 3 + .../libSceFreeType/include/GuestObjects.hpp | 10 + .../prx/libSceFreeType/src/GuestObjects.cpp | 43 +++- core/libs/tests/GuestFreeType.cpp | 206 ++++++++++++++++++ docs/dev/debt/libSceFreeType.md | 2 + 6 files changed, 319 insertions(+), 11 deletions(-) diff --git a/core/libs/prx/libSceFreeType/Export.cpp b/core/libs/prx/libSceFreeType/Export.cpp index bb29de9253..a8919bfb49 100644 --- a/core/libs/prx/libSceFreeType/Export.cpp +++ b/core/libs/prx/libSceFreeType/Export.cpp @@ -6,6 +6,7 @@ #include FT_GASP_H #include FT_CID_H +#include FT_SIZES_H #include #include @@ -33,14 +34,17 @@ void FinalizeSlot(void* object) { void FinalizeSize(void* object) { auto* host = static_cast(object); - auto* face = static_cast(host->generic.data); - if (face->size.generic.finalizer) face->size.generic.finalizer(&face->size); - face->rec.size = nullptr; + auto* rec = static_cast(host->generic.data); + auto* face = static_cast(host->face->generic.data); + SyncSizeRecord_nid_no_patch(*face, *rec, host); + SyncSizePointer_nid_no_patch(*face); + if (rec->generic.finalizer) rec->generic.finalizer(rec); } void FinalizeFace(void* object) { auto* host = static_cast(object); auto* face = static_cast(host->generic.data); + SyncSizePointer_nid_no_patch(*face); if (face->rec.generic.finalizer) face->rec.generic.finalizer(&face->rec); face->finalized = true; } @@ -54,7 +58,7 @@ void PublishFace(GuestLibrary* library, std::unique_ptr face, FT_Face library->faces.push_back(std::move(face)); host->generic = {owner, FinalizeFace}; if (host->glyph) host->glyph->generic = {owner, FinalizeSlot}; - if (host->size) host->size->generic = {owner, FinalizeSize}; + if (host->size) host->size->generic = {&owner->size, FinalizeSize}; *aface = &owner->rec; hostOwner.release(); } @@ -164,6 +168,60 @@ int APS5_VABI FT_Done_Face_nid_postfix(GuestFaceRec* face) { return error; } +int APS5_VABI FT_New_Size_nid_postfix(GuestFaceRec* face, GuestSizeRec** asize) { + GuestFace* owner = FaceOf(face); + FT_Face hostFace = owner ? owner->host : nullptr; + if (!hostFace || !asize || !hostFace->driver) { + FT_Size unused = nullptr; + return FT_New_Size(hostFace, asize ? &unused : nullptr); + } + const bool activates = asize == &owner->rec.size; + *asize = nullptr; + if (activates) hostFace->size = nullptr; + try { + owner->sizes.emplace_back(); + } catch (const std::bad_alloc&) { + return FT_Err_Out_Of_Memory; + } + auto entry = std::prev(owner->sizes.end()); + FT_Size host = nullptr; + const FT_Error error = FT_New_Size(hostFace, activates ? &hostFace->size : &host); + if (activates) host = hostFace->size; + if (error != 0) { + owner->sizes.erase(entry); + SyncSizePointer_nid_no_patch(*owner); + return error; + } + entry->host = host; + SyncSizeRecord_nid_no_patch(*owner, entry->rec, host); + host->generic = {&entry->rec, FinalizeSize}; + *asize = &entry->rec; + SyncSizePointer_nid_no_patch(*owner); + return 0; +} + +int APS5_VABI FT_Activate_Size_nid_postfix(GuestSizeRec* size) { + FT_Size host = size ? static_cast(size->internal) : nullptr; + const FT_Error error = FT_Activate_Size(host); + if (error == 0) SyncSize(*static_cast(host->face->generic.data)); + return error; +} + +int APS5_VABI FT_Done_Size_nid_postfix(GuestSizeRec* size) { + FT_Size host = size ? static_cast(size->internal) : nullptr; + auto* owner = host && host->face ? static_cast(host->face->generic.data) : nullptr; + const FT_Error error = FT_Done_Size(host); + if (error != 0) return error; + if (host == owner->initialSize) { + owner->initialSize = nullptr; + owner->size.internal = nullptr; + } else { + owner->sizes.remove_if([host](const auto& entry) { return entry.host == host; }); + } + SyncSize(*owner); + return 0; +} + int APS5_VABI FT_Set_Pixel_Sizes_nid_postfix(GuestFaceRec* face, std::uint32_t pixel_width, std::uint32_t pixel_height) { GuestFace* owner = FaceOf(face); const FT_Error error = FT_Set_Pixel_Sizes(owner ? owner->host : nullptr, pixel_width, pixel_height); diff --git a/core/libs/prx/libSceFreeType/include/Api.hpp b/core/libs/prx/libSceFreeType/include/Api.hpp index 0c08f7b10f..6b7e44831e 100644 --- a/core/libs/prx/libSceFreeType/include/Api.hpp +++ b/core/libs/prx/libSceFreeType/include/Api.hpp @@ -14,6 +14,9 @@ int APS5_VABI FT_Reference_Face_nid_postfix(FreeType::GuestFaceRec* face); int APS5_VABI FT_Done_Face_nid_postfix(FreeType::GuestFaceRec* face); int APS5_VABI FT_Reference_Face_nid_postfix(FreeType::GuestFaceRec* face); int APS5_VABI FT_Select_Size_nid_postfix(FreeType::GuestFaceRec* face, std::int32_t strikeIndex); +int APS5_VABI FT_New_Size_nid_postfix(FreeType::GuestFaceRec* face, FreeType::GuestSizeRec** asize); +int APS5_VABI FT_Activate_Size_nid_postfix(FreeType::GuestSizeRec* size); +int APS5_VABI FT_Done_Size_nid_postfix(FreeType::GuestSizeRec* size); int APS5_VABI FT_Set_Pixel_Sizes_nid_postfix(FreeType::GuestFaceRec* face, std::uint32_t pixelWidth, std::uint32_t pixelHeight); int APS5_VABI FT_Set_Char_Size_nid_postfix(FreeType::GuestFaceRec* face, std::int64_t charWidth, std::int64_t charHeight, std::uint32_t horizontalResolution, std::uint32_t verticalResolution); std::uint32_t APS5_VABI FT_Get_Char_Index_nid_postfix(FreeType::GuestFaceRec* face, std::uint64_t charcode); diff --git a/core/libs/prx/libSceFreeType/include/GuestObjects.hpp b/core/libs/prx/libSceFreeType/include/GuestObjects.hpp index 081d70665f..d47c3c1037 100644 --- a/core/libs/prx/libSceFreeType/include/GuestObjects.hpp +++ b/core/libs/prx/libSceFreeType/include/GuestObjects.hpp @@ -21,6 +21,11 @@ struct GuestLibrary { void UnregisterBitmap_nid_no_patch(const GuestBitmap* bitmap); +struct GuestSize { + GuestSizeRec rec{}; + FT_Size host = nullptr; +}; + struct GuestFace { GuestFaceRec rec{}; GuestGlyphSlotRec slot{}; @@ -28,6 +33,9 @@ struct GuestFace { GuestLibrary* library = nullptr; FT_Face host = nullptr; bool finalized = false; + FT_Size initialSize = nullptr; + bool sizeInitialized = false; + std::list sizes; std::vector bytes; std::vector fixedSizes; std::vector charmaps; @@ -43,6 +51,8 @@ GuestFace* FaceOf(const GuestFaceRec* face); GuestFace* FaceOf(const GuestGlyphSlotRec* slot); void SyncFace(GuestFace& face); void SyncSize(GuestFace& face); +void SyncSizePointer_nid_no_patch(GuestFace& face); +void SyncSizeRecord_nid_no_patch(GuestFace& face, GuestSizeRec& rec, FT_Size host); void SyncSlot(GuestFace& face); } diff --git a/core/libs/prx/libSceFreeType/src/GuestObjects.cpp b/core/libs/prx/libSceFreeType/src/GuestObjects.cpp index 17d1888a21..1ecb345092 100644 --- a/core/libs/prx/libSceFreeType/src/GuestObjects.cpp +++ b/core/libs/prx/libSceFreeType/src/GuestObjects.cpp @@ -1,6 +1,7 @@ #include "prx/libSceFreeType/include/GuestObjects.hpp" #include +#include namespace FreeType { @@ -51,20 +52,48 @@ void SyncFace(GuestFace& face) { rec.underline_position = host->underline_position; rec.underline_thickness = host->underline_thickness; rec.glyph = host->glyph ? &face.slot : nullptr; - rec.size = host->size ? &face.size : nullptr; rec.internal = &face; SyncSize(face); SyncSlot(face); } -void SyncSize(GuestFace& face) { - face.size.face = &face.rec; - if (!face.host->size) { - face.size.metrics = {}; +void SyncSizeRecord_nid_no_patch(GuestFace& face, GuestSizeRec& rec, FT_Size host) { + rec.face = &face.rec; + rec.internal = host; + if (!host) { + rec.metrics = {}; return; } - const auto& metrics = face.host->size->metrics; - face.size.metrics = {metrics.x_ppem, metrics.y_ppem, metrics.x_scale, metrics.y_scale, metrics.ascender, metrics.descender, metrics.height, metrics.max_advance}; + const auto& metrics = host->metrics; + rec.metrics = {metrics.x_ppem, metrics.y_ppem, metrics.x_scale, metrics.y_scale, metrics.ascender, metrics.descender, metrics.height, metrics.max_advance}; +} + +void SyncSizePointer_nid_no_patch(GuestFace& face) { + const FT_Size active = face.host->size; + if (!active) { + face.rec.size = nullptr; + return; + } + if (active == face.initialSize) { + face.rec.size = &face.size; + return; + } + for (auto& size : face.sizes) { + if (size.host != active) continue; + face.rec.size = &size.rec; + return; + } + throw std::runtime_error("FreeType: an active size created outside the guest bridge is not implemented"); +} + +void SyncSize(GuestFace& face) { + if (!face.sizeInitialized) { + face.initialSize = face.host->size; + face.sizeInitialized = true; + SyncSizeRecord_nid_no_patch(face, face.size, face.initialSize); + } + SyncSizePointer_nid_no_patch(face); + if (face.rec.size) SyncSizeRecord_nid_no_patch(face, *face.rec.size, face.host->size); } void SyncSlot(GuestFace& face) { diff --git a/core/libs/tests/GuestFreeType.cpp b/core/libs/tests/GuestFreeType.cpp index 86df877d10..15065a9bf6 100644 --- a/core/libs/tests/GuestFreeType.cpp +++ b/core/libs/tests/GuestFreeType.cpp @@ -9,6 +9,8 @@ #include FT_OUTLINE_H #include FT_TRIGONOMETRY_H #include FT_CID_H +#include FT_SIZES_H +#include FT_SYSTEM_H #include #include @@ -1257,7 +1259,211 @@ static void CheckTransforms(const std::vector& font) { std::printf("FT_Set_Transform: %zu native glyph and bitmap comparisons\n", comparisons); } +static void SizeMatches(const GuestSizeRec* size, FT_Size native) { + Require(size != nullptr && native != nullptr); + const auto& a = size->metrics; + const auto& b = native->metrics; + Require(a.x_ppem == b.x_ppem && a.y_ppem == b.y_ppem && a.x_scale == b.x_scale && a.y_scale == b.y_scale); + Require(a.ascender == b.ascender && a.descender == b.descender && a.height == b.height && a.max_advance == b.max_advance); +} + +struct SizeEvents { + std::vector sizes; + std::vector> calls; + + int Index(void* size) const { + if (!size) return -1; + for (std::size_t index = 0; index < sizes.size(); ++index) { + if (sizes[index] == size) return static_cast(index); + } + Require(false); + return -2; + } +}; + +static void NativeMultipleSizeFinalizer(void* object) { + auto* size = static_cast(object); + auto& events = *static_cast(size->generic.data); + events.calls.push_back({events.Index(size), events.Index(size->face->size), size->metrics.y_ppem, size->face->glyph == nullptr}); +} + +static void APS5_VABI GuestMultipleSizeFinalizer(void* object) { + auto* size = static_cast(object); + auto* face = static_cast(size->face); + auto& events = *static_cast(size->generic.data); + events.calls.push_back({events.Index(size), events.Index(face->size), size->metrics.y_ppem, face->glyph == nullptr}); +} + +static void NativeMultipleSizeFaceFinalizer(void* object) { + auto* face = static_cast(object); + Require(face->glyph == nullptr && face->size == nullptr); + static_cast(face->generic.data)->calls.push_back({100, -1, 0, 1}); +} + +static void APS5_VABI GuestMultipleSizeFaceFinalizer(void* object) { + auto* face = static_cast(object); + Require(face->glyph == nullptr && face->size == nullptr); + static_cast(face->generic.data)->calls.push_back({100, -1, 0, 1}); +} + +static FT_Alloc_Func SizeOriginalAlloc = nullptr; +static int SizeAllocationsBeforeFailure = 0; + +static void* SizeFailingAlloc(FT_Memory memory, long bytes) { + if (SizeAllocationsBeforeFailure-- == 0) return nullptr; + return SizeOriginalAlloc(memory, bytes); +} + +static void SizeObjects(const std::vector& font) { + Require(FT_New_Size_nid_postfix(nullptr, nullptr) == FT_New_Size(nullptr, nullptr)); + Require(FT_Done_Size_nid_postfix(nullptr) == FT_Done_Size(nullptr)); + Require(FT_Activate_Size_nid_postfix(nullptr) == FT_Activate_Size(nullptr)); + GuestSizeRec sentinel{}; + GuestSizeRec* untouched = &sentinel; + Require(FT_New_Size_nid_postfix(nullptr, &untouched) == FT_Err_Invalid_Face_Handle && untouched == &sentinel); + FT_FaceRec driverless{}; + GuestFace invalidOwner; + invalidOwner.host = &driverless; + invalidOwner.rec.internal = &invalidOwner; + FT_Size nativeUntouched = reinterpret_cast(&sentinel); + Require(FT_New_Size(&driverless, &nativeUntouched) == FT_Err_Invalid_Driver_Handle); + Require(FT_New_Size_nid_postfix(&invalidOwner.rec, &untouched) == FT_Err_Invalid_Driver_Handle && untouched == &sentinel); + Require(FT_New_Size_nid_postfix(&invalidOwner.rec, nullptr) == FT_New_Size(&driverless, nullptr)); + + FT_SizeRec driverlessSize{}; + GuestSizeRec invalidSize{}; + invalidSize.internal = &driverlessSize; + Require(FT_Done_Size_nid_postfix(&invalidSize) == FT_Done_Size(&driverlessSize)); + Require(FT_Activate_Size_nid_postfix(&invalidSize) == FT_Activate_Size(&driverlessSize)); + driverlessSize.face = &driverless; + Require(FT_Done_Size_nid_postfix(&invalidSize) == FT_Done_Size(&driverlessSize)); + Require(FT_Activate_Size_nid_postfix(&invalidSize) == FT_Activate_Size(&driverlessSize)); + static_assert(sizeof(GuestSizeMetrics) == 56 && sizeof(GuestSizeRec) == 88); + + int scenarios = 0; + int rendered = 0; + int failures = 0; + for (const int faceIndex : {0, -1}) { + for (const int count : {0, 1, 5, 41}) { + for (const int cleanup : {0, 1, 2}) { + void* library = nullptr; + FT_Library oracle = nullptr; + Require(FT_Init_FreeType_nid_postfix(&library) == 0 && FT_Init_FreeType(&oracle) == 0); + GuestFaceRec* face = nullptr; + FT_Face native = nullptr; + Require(FT_New_Memory_Face_nid_postfix(library, font.data(), font.size(), faceIndex, &face) == 0); + Require(FT_New_Memory_Face(oracle, font.data(), static_cast(font.size()), faceIndex, &native) == 0); + SizeEvents actual; + SizeEvents expected; + face->generic = {&actual, GuestMultipleSizeFaceFinalizer}; + native->generic = {&expected, NativeMultipleSizeFaceFinalizer}; + std::vector sizes; + std::vector originals; + auto remember = [&](GuestSizeRec* size, FT_Size original) { + Require(size->face == face && original->face == native); + actual.sizes.push_back(size); + expected.sizes.push_back(original); + size->generic = {&actual, GuestMultipleSizeFinalizer}; + original->generic = {&expected, NativeMultipleSizeFinalizer}; + sizes.push_back(size); + originals.push_back(original); + SizeMatches(size, original); + }; + if (native->size) remember(face->size, native->size); + Require(FT_New_Size_nid_postfix(face, nullptr) == FT_New_Size(native, nullptr)); + for (int index = 0; index < count; ++index) { + GuestSizeRec* before = face->size; + FT_Size previous = native->size; + GuestSizeRec* created = nullptr; + FT_Size original = nullptr; + Require(FT_New_Size_nid_postfix(face, &created) == FT_New_Size(native, &original)); + Require(created != nullptr && original != nullptr && face->size == before && native->size == previous); + remember(created, original); + } + if (count == 5) { + Require(FT_New_Size_nid_postfix(face, &face->size) == FT_New_Size(native, &native->size)); + remember(face->size, native->size); + } + for (std::size_t index = 0; index < sizes.size(); ++index) { + Require(FT_Activate_Size_nid_postfix(sizes[index]) == 0 && FT_Activate_Size(originals[index]) == 0); + Require(face->size == sizes[index] && native->size == originals[index]); + Require(FT_Set_Pixel_Sizes_nid_postfix(face, 0, 12 + index) == FT_Set_Pixel_Sizes(native, 0, 12 + index)); + SizeMatches(sizes[index], originals[index]); + } + for (std::size_t turn = 0; turn < sizes.size() * 3; ++turn) { + const std::size_t index = (turn * 17 + 3) % sizes.size(); + Require(FT_Activate_Size_nid_postfix(sizes[index]) == FT_Activate_Size(originals[index])); + Require(face->size == sizes[index]); + for (std::size_t other = 0; other < sizes.size(); ++other) SizeMatches(sizes[other], originals[other]); + if (!native->glyph) continue; + Require(FT_Load_Char_nid_postfix(face, 'A', FT_LOAD_NO_HINTING) == FT_Load_Char(native, 'A', FT_LOAD_NO_HINTING)); + for (unsigned point = 0; point < face->glyph->outline.n_points; ++point) face->glyph->outline.points[point].x += 64; + FT_Outline_Translate(&native->glyph->outline, 64, 0); + Require(FT_Activate_Size_nid_postfix(sizes[index]) == FT_Activate_Size(originals[index])); + Require(FT_Render_Glyph_nid_postfix(face->glyph, FT_RENDER_MODE_NORMAL) == FT_Render_Glyph(native->glyph, FT_RENDER_MODE_NORMAL)); + SlotMatches(face->glyph, native->glyph); + ++rendered; + } + if (cleanup == 0) { + while (!sizes.empty()) { + const std::size_t index = sizes.size() % 2 == 0 ? 0 : sizes.size() - 1; + const int removed = actual.Index(sizes[index]); + Require(FT_Done_Size_nid_postfix(sizes[index]) == FT_Done_Size(originals[index])); + actual.sizes[removed] = nullptr; + expected.sizes[removed] = nullptr; + sizes.erase(sizes.begin() + index); + originals.erase(originals.begin() + index); + Require(actual.Index(face->size) == expected.Index(native->size) && actual.calls == expected.calls); + } + Require(face->size == nullptr && native->size == nullptr); + Require(FT_Set_Pixel_Sizes_nid_postfix(face, 0, 24) == FT_Set_Pixel_Sizes(native, 0, 24)); + Require(FT_New_Size_nid_postfix(face, &face->size) == FT_New_Size(native, &native->size)); + Require(face->size != nullptr && native->size != nullptr); + remember(face->size, native->size); + Require(FT_Set_Pixel_Sizes_nid_postfix(face, 0, 24) == FT_Set_Pixel_Sizes(native, 0, 24)); + for (const bool alias : {false, true}) { + for (const int allocation : {0, 1, 2}) { + auto* owner = static_cast(face->internal); + FT_Memory memory = owner->host->memory; + SizeOriginalAlloc = memory->alloc; + SizeAllocationsBeforeFailure = allocation; + memory->alloc = SizeFailingAlloc; + GuestSizeRec* output = &sentinel; + const int error = FT_New_Size_nid_postfix(face, alias ? &face->size : &output); + memory->alloc = SizeOriginalAlloc; + Require(error == FT_Err_Out_Of_Memory && owner->sizes.size() == 1); + FT_Memory nativeMemory = native->memory; + SizeOriginalAlloc = nativeMemory->alloc; + SizeAllocationsBeforeFailure = allocation; + nativeMemory->alloc = SizeFailingAlloc; + FT_Size nativeOutput = reinterpret_cast(&sentinel); + const int nativeError = FT_New_Size(native, alias ? &native->size : &nativeOutput); + nativeMemory->alloc = SizeOriginalAlloc; + Require(error == nativeError); + Require(alias ? face->size == nullptr && native->size == nullptr : output == nullptr && nativeOutput == nullptr); + Require(actual.Index(face->size) == expected.Index(native->size)); + Require(actual.calls == expected.calls); + ++failures; + } + } + } + if (cleanup == 1) { + Require(FT_Reference_Face_nid_postfix(face) == 0 && FT_Reference_Face(native) == 0); + Require(FT_Done_Face_nid_postfix(face) == 0 && FT_Done_Face(native) == 0); + Require(actual.calls.empty() && expected.calls.empty()); + } + if (cleanup != 2) Require(FT_Done_Face_nid_postfix(face) == 0 && FT_Done_Face(native) == 0); + Require(FT_Done_FreeType_nid_postfix(library) == 0 && FT_Done_FreeType(oracle) == 0); + Require(actual.calls == expected.calls && !actual.calls.empty() && actual.calls.back()[0] == 100); + ++scenarios; + } + } + } + std::printf("FreeType size objects: %d lifecycles, %d rendered glyphs, %d allocation failures compared with native\n", scenarios, rendered, failures); +} + int main() { + SizeObjects(SquareGlyphFont('A', 700)); { const auto referenceFont = SquareGlyphFont('A', 700); GuestLibrary* referenceLibrary = nullptr; diff --git a/docs/dev/debt/libSceFreeType.md b/docs/dev/debt/libSceFreeType.md index f52d05e5d0..205ee0df69 100644 --- a/docs/dev/debt/libSceFreeType.md +++ b/docs/dev/debt/libSceFreeType.md @@ -1,6 +1,8 @@ # libSceFreeType technical debt ### Unknown function info +- [FT_New_Size, FT_Activate_Size and FT_Done_Size](../../../core/libs/prx/libSceFreeType/Export.cpp) (libSceFreeType) - console signatures, errors and generic-finalizer ordering are unverified; the LP64 guest adapter follows bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftobjs.c). Private size fields are bridge-owned. Sizes created outside the bridge throw when selected. + - [FT_Reference_Face](../../../core/libs/prx/libSceFreeType/Export.cpp) (libSceFreeType) - console signature, reference-counter limits and finalizer behavior are unverified; the LP64 guest adapter follows bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftobjs.c). Counter overflow and reentrant finalization have not been measured on the console. - [FT_Get_Sfnt_Name_Count, FT_Get_Sfnt_Name](../../../core/libs/prx/libSceFreeType/src/Names.cpp) (libSceFreeType) - console signatures, record layout and errors are unverified; the adapter follows bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftsnames.c) and assumes the public LP64 `FT_SfntName` layout (24 bytes, string pointer at 8, byte length at 16). Strings retain their original encoding, borrow native face storage and remain valid only while the face is alive From e0b244d9de4b4e8ea2cc839d20ce6fab422d0939 Mon Sep 17 00:00:00 2001 From: Ondra97 <47064087+Ondra97@users.noreply.github.com> Date: Thu, 22 Oct 2026 08:09:55 +0200 Subject: [PATCH 3/4] test(libSceFreeType): pin size ownership through teardown --- core/libs/tests/GuestFreeType.cpp | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/core/libs/tests/GuestFreeType.cpp b/core/libs/tests/GuestFreeType.cpp index 15065a9bf6..ed1987ef28 100644 --- a/core/libs/tests/GuestFreeType.cpp +++ b/core/libs/tests/GuestFreeType.cpp @@ -1267,6 +1267,14 @@ static void SizeMatches(const GuestSizeRec* size, FT_Size native) { Require(a.ascender == b.ascender && a.descender == b.descender && a.height == b.height && a.max_advance == b.max_advance); } +static bool OwnsSize(const GuestFace& face, const GuestSizeRec* size) { + if (size == &face.size) return true; + for (const auto& entry : face.sizes) { + if (size == &entry.rec) return true; + } + return false; +} + struct SizeEvents { std::vector sizes; std::vector> calls; @@ -1290,6 +1298,10 @@ static void NativeMultipleSizeFinalizer(void* object) { static void APS5_VABI GuestMultipleSizeFinalizer(void* object) { auto* size = static_cast(object); auto* face = static_cast(size->face); + const auto& owner = *static_cast(face->internal); + Require(OwnsSize(owner, size)); + if (face->size) Require(OwnsSize(owner, face->size)); + SizeMatches(size, static_cast(size->internal)); auto& events = *static_cast(size->generic.data); events.calls.push_back({events.Index(size), events.Index(face->size), size->metrics.y_ppem, face->glyph == nullptr}); } @@ -1346,7 +1358,7 @@ static void SizeObjects(const std::vector& font) { for (const int faceIndex : {0, -1}) { for (const int count : {0, 1, 5, 41}) { for (const int cleanup : {0, 1, 2}) { - void* library = nullptr; + GuestLibrary* library = nullptr; FT_Library oracle = nullptr; Require(FT_Init_FreeType_nid_postfix(&library) == 0 && FT_Init_FreeType(&oracle) == 0); GuestFaceRec* face = nullptr; @@ -1452,9 +1464,18 @@ static void SizeObjects(const std::vector& font) { Require(FT_Done_Face_nid_postfix(face) == 0 && FT_Done_Face(native) == 0); Require(actual.calls.empty() && expected.calls.empty()); } + if (cleanup == 2 && count == 5) { + Require(sizes.size() > 2); + Require(FT_Activate_Size_nid_postfix(sizes[1]) == FT_Activate_Size(originals[1])); + Require(face->size == sizes[1] && native->size == originals[1]); + } if (cleanup != 2) Require(FT_Done_Face_nid_postfix(face) == 0 && FT_Done_Face(native) == 0); Require(FT_Done_FreeType_nid_postfix(library) == 0 && FT_Done_FreeType(oracle) == 0); Require(actual.calls == expected.calls && !actual.calls.empty() && actual.calls.back()[0] == 100); + if (cleanup == 2 && count == 5) { + Require(actual.calls.size() > 3 && actual.calls[0][1] == 1 && actual.calls[1][0] == 1); + for (std::size_t index = 2; index + 1 < actual.calls.size(); ++index) Require(actual.calls[index][1] == 1); + } ++scenarios; } } From ad2b0857790f1e1466dee1fee86de94ce201addc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sun, 25 Oct 2026 19:46:07 +0100 Subject: [PATCH 4/4] chore(libSceFreeType): drop duplicate FT_Reference_Face declaration --- core/libs/prx/libSceFreeType/include/Api.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/core/libs/prx/libSceFreeType/include/Api.hpp b/core/libs/prx/libSceFreeType/include/Api.hpp index 6b7e44831e..ac044b7919 100644 --- a/core/libs/prx/libSceFreeType/include/Api.hpp +++ b/core/libs/prx/libSceFreeType/include/Api.hpp @@ -12,7 +12,6 @@ int APS5_VABI FT_New_Face_nid_postfix(FreeType::GuestLibrary* library, const cha int APS5_VABI FT_New_Memory_Face_nid_postfix(FreeType::GuestLibrary* library, const unsigned char* fileBase, std::int64_t fileSize, std::int64_t faceIndex, FreeType::GuestFaceRec** aface); int APS5_VABI FT_Reference_Face_nid_postfix(FreeType::GuestFaceRec* face); int APS5_VABI FT_Done_Face_nid_postfix(FreeType::GuestFaceRec* face); -int APS5_VABI FT_Reference_Face_nid_postfix(FreeType::GuestFaceRec* face); int APS5_VABI FT_Select_Size_nid_postfix(FreeType::GuestFaceRec* face, std::int32_t strikeIndex); int APS5_VABI FT_New_Size_nid_postfix(FreeType::GuestFaceRec* face, FreeType::GuestSizeRec** asize); int APS5_VABI FT_Activate_Size_nid_postfix(FreeType::GuestSizeRec* size);