Merge pull request #8247 from santhoshkumar-dev/codex/freetype-font-face-lifecycle

feat(libSceFreeType): support memory fonts and face cleanup
This commit is contained in:
Adrià authored and GitHub committed 2026-10-21 14:03:13 +02:00
commit f815e8bdd6
6 files changed
+223 -13

No files matched your search

+65 -7
View File
@@ -15,6 +15,40 @@ using namespace FreeType;
namespace {
void FinalizeSlot(void* object) {
auto* host = static_cast<FT_GlyphSlot>(object);
auto* face = static_cast<GuestFace*>(host->generic.data);
face->rec.glyph = nullptr;
if (face->slot.generic.finalizer) face->slot.generic.finalizer(&face->slot);
}
void FinalizeSize(void* object) {
auto* host = static_cast<FT_Size>(object);
auto* face = static_cast<GuestFace*>(host->generic.data);
if (face->size.generic.finalizer) face->size.generic.finalizer(&face->size);
face->rec.size = nullptr;
}
void FinalizeFace(void* object) {
auto* host = static_cast<FT_Face>(object);
auto* face = static_cast<GuestFace*>(host->generic.data);
if (face->rec.generic.finalizer) face->rec.generic.finalizer(&face->rec);
}
void PublishFace(GuestLibrary* library, std::unique_ptr<GuestFace> face, FT_Face host, GuestFaceRec** aface) {
std::unique_ptr<FT_FaceRec, decltype(&FT_Done_Face)> hostOwner(host, &FT_Done_Face);
face->library = library;
face->host = host;
SyncFace(*face);
auto* owner = face.get();
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};
*aface = &owner->rec;
hostOwner.release();
}
bool ReadFontFile(const char* path, std::vector<unsigned char>& bytes) {
std::filesystem::path hostPath;
if (ResolvePathError_nid_no_patch(path, hostPath) != 0) return false;
@@ -58,13 +92,19 @@ extern "C" {
int APS5_VABI FT_Init_FreeType_nid_postfix(GuestLibrary** alibrary) {
if (!alibrary) return FT_Err_Invalid_Argument;
FT_Library host = nullptr;
const FT_Error error = FT_Init_FreeType(&host);
auto library = std::make_unique<GuestLibrary>();
const FT_Error error = FT_Init_FreeType(&library->host);
if (error != 0) return error;
*alibrary = new GuestLibrary{host};
*alibrary = library.release();
return 0;
}
int APS5_VABI FT_Done_FreeType_nid_postfix(GuestLibrary* library) {
const FT_Error error = FT_Done_FreeType(library ? library->host : nullptr);
if (error == 0) delete library;
return error;
}
int APS5_VABI FT_New_Face_nid_postfix(GuestLibrary* library, const char* filepathname, std::int64_t face_index, GuestFaceRec** aface) {
if (!filepathname) return FT_Err_Invalid_Argument;
const std::int64_t faceIndex = OpenFaceIndex(face_index);
@@ -77,13 +117,31 @@ int APS5_VABI FT_New_Face_nid_postfix(GuestLibrary* library, const char* filepat
FT_Face host = nullptr;
const FT_Error error = FT_New_Memory_Face(library->host, face->bytes.data(), static_cast<FT_Long>(face->bytes.size()), static_cast<FT_Long>(faceIndex), aface ? &host : nullptr);
if (error != 0 || !aface) return error;
face->library = library;
face->host = host;
SyncFace(*face);
*aface = &face.release()->rec;
PublishFace(library, std::move(face), host, aface);
return 0;
}
int APS5_VABI FT_New_Memory_Face_nid_postfix(GuestLibrary* library, const unsigned char* fileBase, std::int64_t fileSize, std::int64_t faceIndex, GuestFaceRec** aface) {
if (!fileBase) return FT_Err_Invalid_Argument;
if (!library) return FT_Err_Invalid_Library_Handle;
if (fileSize < std::numeric_limits<FT_Long>::min() || fileSize > std::numeric_limits<FT_Long>::max()) {
throw std::runtime_error("FT_New_Memory_Face: a font buffer size outside the host FT_Long range is not implemented");
}
auto face = std::make_unique<GuestFace>();
FT_Face host = nullptr;
const FT_Error error = FT_New_Memory_Face(library->host, fileBase, static_cast<FT_Long>(fileSize), static_cast<FT_Long>(OpenFaceIndex(faceIndex)), aface ? &host : nullptr);
if (error != 0 || !aface) return error;
PublishFace(library, std::move(face), host, aface);
return 0;
}
int APS5_VABI FT_Done_Face_nid_postfix(GuestFaceRec* face) {
GuestFace* owner = FaceOf(face);
const FT_Error error = FT_Done_Face(owner ? owner->host : nullptr);
if (error == 0) owner->library->faces.remove_if([owner](const auto& entry) { return entry.get() == owner; });
return error;
}
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);
@@ -1,6 +1,8 @@
#ifndef CORE_LIBS_PRX_LIBSCEFREETYPE_INCLUDE_GUESTFREETYPE_HPP
#define CORE_LIBS_PRX_LIBSCEFREETYPE_INCLUDE_GUESTFREETYPE_HPP
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstddef>
#include <cstdint>
@@ -8,7 +10,7 @@ namespace FreeType {
struct GuestGeneric {
void* data;
void* finalizer;
void (APS5_VABI *finalizer)(void*);
};
struct GuestVector {
@@ -147,6 +149,7 @@ struct GuestFaceRec {
void* internal;
};
static_assert(sizeof(GuestGeneric) == 16 && offsetof(GuestGeneric, finalizer) == 8);
static_assert(sizeof(GuestBitmapSize) == 32 && offsetof(GuestBitmapSize, size) == 8);
static_assert(sizeof(GuestCharMapRec) == 16 && offsetof(GuestCharMapRec, platform_id) == 12);
static_assert(sizeof(GuestSizeMetrics) == 56 && offsetof(GuestSizeMetrics, x_scale) == 8);
@@ -6,12 +6,17 @@
#include <ft2build.h>
#include FT_FREETYPE_H
#include <list>
#include <memory>
#include <vector>
namespace FreeType {
struct GuestFace;
struct GuestLibrary {
FT_Library host = nullptr;
std::list<std::unique_ptr<GuestFace>> faces;
};
struct GuestFace {
@@ -49,8 +49,8 @@ void SyncFace(GuestFace& face) {
rec.max_advance_height = host->max_advance_height;
rec.underline_position = host->underline_position;
rec.underline_thickness = host->underline_thickness;
rec.glyph = &face.slot;
rec.size = &face.size;
rec.glyph = host->glyph ? &face.slot : nullptr;
rec.size = host->size ? &face.size : nullptr;
rec.internal = &face;
SyncSize(face);
SyncSlot(face);
+142 -2
View File
@@ -24,7 +24,10 @@ using namespace FreeType;
extern "C" {
int APS5_VABI FT_Init_FreeType_nid_postfix(void**);
int APS5_VABI FT_Done_FreeType_nid_postfix(void*);
int APS5_VABI FT_New_Face_nid_postfix(void*, const char*, std::int64_t, GuestFaceRec**);
int APS5_VABI FT_New_Memory_Face_nid_postfix(void*, const unsigned char*, std::int64_t, std::int64_t, GuestFaceRec**);
int APS5_VABI FT_Done_Face_nid_postfix(GuestFaceRec*);
int APS5_VABI FT_Set_Pixel_Sizes_nid_postfix(GuestFaceRec*, std::uint32_t, std::uint32_t);
std::uint32_t APS5_VABI FT_Get_Char_Index_nid_postfix(GuestFaceRec*, std::uint64_t);
int APS5_VABI FT_Load_Glyph_nid_postfix(GuestFaceRec*, std::uint32_t, std::int32_t);
@@ -86,6 +89,58 @@ static void SlotMatches(const GuestGlyphSlotRec* slot, FT_GlyphSlot host) {
Require(slot->lsb_delta == host->lsb_delta && slot->rsb_delta == host->rsb_delta && slot->control_len == host->control_len);
}
struct Finalizers {
std::vector<int> calls;
};
static void NativeSlotFinalizer(void* object) {
auto* slot = static_cast<FT_GlyphSlot>(object);
Require(slot->face->glyph == nullptr);
static_cast<Finalizers*>(slot->generic.data)->calls.push_back(1);
}
static void NativeSizeFinalizer(void* object) {
auto* size = static_cast<FT_Size>(object);
Require(size->face->size == size);
static_cast<Finalizers*>(size->generic.data)->calls.push_back(2);
}
static void NativeFaceFinalizer(void* object) {
auto* face = static_cast<FT_Face>(object);
Require(face->glyph == nullptr && face->size == nullptr);
static_cast<Finalizers*>(face->generic.data)->calls.push_back(3);
}
static void APS5_VABI GuestSlotFinalizer(void* object) {
auto* slot = static_cast<GuestGlyphSlotRec*>(object);
Require(static_cast<GuestFaceRec*>(slot->face)->glyph == nullptr);
static_cast<Finalizers*>(slot->generic.data)->calls.push_back(1);
}
static void APS5_VABI GuestSizeFinalizer(void* object) {
auto* size = static_cast<GuestSizeRec*>(object);
Require(static_cast<GuestFaceRec*>(size->face)->size == size);
static_cast<Finalizers*>(size->generic.data)->calls.push_back(2);
}
static void APS5_VABI GuestFaceFinalizer(void* object) {
auto* face = static_cast<GuestFaceRec*>(object);
Require(face->glyph == nullptr && face->size == nullptr);
static_cast<Finalizers*>(face->generic.data)->calls.push_back(3);
}
static void SetFinalizers(FT_Face face, Finalizers& events) {
face->generic = {&events, NativeFaceFinalizer};
face->glyph->generic = {&events, NativeSlotFinalizer};
face->size->generic = {&events, NativeSizeFinalizer};
}
static void SetFinalizers(GuestFaceRec* face, Finalizers& events) {
face->generic = {&events, GuestFaceFinalizer};
face->glyph->generic = {&events, GuestSlotFinalizer};
face->size->generic = {&events, GuestSizeFinalizer};
}
int main() {
const auto root = std::filesystem::temp_directory_path() / ("anyps5_guest_freetype-" + std::to_string(std::random_device{}()));
std::filesystem::create_directories(root / "app0");
@@ -108,6 +163,8 @@ int main() {
Require(FT_Init_FreeType_nid_postfix(nullptr) == FT_Err_Invalid_Argument);
void* library = nullptr;
Require(FT_Init_FreeType_nid_postfix(&library) == 0 && library != nullptr);
Require(FT_Done_FreeType_nid_postfix(nullptr) == FT_Err_Invalid_Library_Handle);
Require(FT_Done_Face_nid_postfix(nullptr) == FT_Err_Invalid_Face_Handle);
GuestFaceRec* face = nullptr;
Require(FT_New_Face_nid_postfix(library, nullptr, 0, &face) == FT_Err_Invalid_Argument);
@@ -122,6 +179,54 @@ int main() {
Require(FT_New_Face_nid_postfix(library, "/app0/font.ttf", 0, &face) == 0 && face != nullptr && face != masked);
FaceMatches(face, host, library);
const auto fontSize = static_cast<std::int64_t>(font.size());
GuestFaceRec* memoryFace = nullptr;
Require(FT_New_Memory_Face_nid_postfix(library, nullptr, fontSize, 0, &memoryFace) == FT_Err_Invalid_Argument);
Require(FT_New_Memory_Face_nid_postfix(nullptr, font.data(), fontSize, 0, &memoryFace) == FT_Err_Invalid_Library_Handle);
Require(FT_New_Memory_Face_nid_postfix(library, font.data(), fontSize, 0, nullptr) == FT_Err_Invalid_Argument);
Require(FT_New_Memory_Face_nid_postfix(library, font.data(), fontSize, -1, nullptr) == 0);
FT_Face nativeProbe = nullptr;
GuestFaceRec* probe = nullptr;
Require(FT_New_Memory_Face(hostLibrary, font.data(), static_cast<FT_Long>(font.size()), -1, &nativeProbe) == 0);
Require(FT_New_Memory_Face_nid_postfix(library, font.data(), fontSize, -1, &probe) == 0 && probe != nullptr);
Require(probe->num_faces == nativeProbe->num_faces && probe->face_index == nativeProbe->face_index);
Require((probe->glyph == nullptr) == (nativeProbe->glyph == nullptr) && (probe->size == nullptr) == (nativeProbe->size == nullptr));
Finalizers nativeProbeEvents;
Finalizers guestProbeEvents;
nativeProbe->generic = {&nativeProbeEvents, NativeFaceFinalizer};
probe->generic = {&guestProbeEvents, GuestFaceFinalizer};
Require(FT_Done_Face(nativeProbe) == 0 && FT_Done_Face_nid_postfix(probe) == 0);
Require(nativeProbeEvents.calls == std::vector<int>({3}) && guestProbeEvents.calls == nativeProbeEvents.calls);
Require(FT_New_Memory_Face_nid_postfix(library, font.data(), fontSize, 1, &memoryFace) == secondFace && memoryFace == nullptr);
Require(FT_New_Memory_Face_nid_postfix(library, font.data(), fontSize, 0x80000001ll, &memoryFace) == secondFace && memoryFace == nullptr);
FT_Face invalidHost = nullptr;
for (const FT_Long length : {0L, 4L}) {
const FT_Error expected = FT_New_Memory_Face(hostLibrary, font.data(), length, 0, &invalidHost);
Require(expected != 0 && invalidHost == nullptr);
Require(FT_New_Memory_Face_nid_postfix(library, font.data(), length, 0, &memoryFace) == expected && memoryFace == nullptr);
}
const unsigned char invalidFont[32]{};
const FT_Error invalidFormat = FT_New_Memory_Face(hostLibrary, invalidFont, sizeof(invalidFont), 0, &invalidHost);
Require(invalidFormat != 0 && invalidHost == nullptr);
Require(FT_New_Memory_Face_nid_postfix(library, invalidFont, sizeof(invalidFont), 0, &memoryFace) == invalidFormat && memoryFace == nullptr);
if constexpr (sizeof(FT_Long) < sizeof(std::int64_t)) {
for (const std::int64_t length : {0x80000000ll, -0x80000001ll}) {
Require(ThrowsWith([&] { FT_New_Memory_Face_nid_postfix(library, font.data(), length, 0, &memoryFace); },
"FT_New_Memory_Face: a font buffer size outside the host FT_Long range is not implemented"));
Require(memoryFace == nullptr);
}
}
Require(FT_New_Memory_Face_nid_postfix(library, font.data(), fontSize, 0x180000000ll, &memoryFace) == 0 && memoryFace != nullptr);
FaceMatches(memoryFace, host, library);
Require(memoryFace != face && memoryFace->glyph != face->glyph);
Require(FT_Set_Pixel_Sizes_nid_postfix(memoryFace, 0, 100) == 0 && FT_Set_Pixel_Sizes(host, 0, 100) == 0);
Require(FT_Get_Char_Index_nid_postfix(memoryFace, 'A') == 1);
Require(FT_Load_Glyph_nid_postfix(memoryFace, 1, FT_LOAD_DEFAULT) == 0 && FT_Load_Glyph(host, 1, FT_LOAD_DEFAULT) == 0);
SlotMatches(memoryFace->glyph, host->glyph);
Require(FT_Render_Glyph_nid_postfix(memoryFace->glyph, FT_RENDER_MODE_NORMAL) == 0 && FT_Render_Glyph(host->glyph, FT_RENDER_MODE_NORMAL) == 0);
SlotMatches(memoryFace->glyph, host->glyph);
Require(face->size->metrics.y_ppem == 0);
Require(FT_Set_Pixel_Sizes_nid_postfix(nullptr, 0, 100) == FT_Err_Invalid_Face_Handle);
Require(FT_Set_Pixel_Sizes_nid_postfix(face, 0, 100) == 0 && FT_Set_Pixel_Sizes(host, 0, 100) == 0);
const auto& metrics = host->size->metrics;
@@ -197,8 +302,43 @@ int main() {
slot->outline.points = original;
Require(FT_Render_Glyph_nid_postfix(slot, FT_RENDER_MODE_NORMAL) == 0 && slot->bitmap_left == 10);
FT_Done_Face(host);
FT_Done_FreeType(hostLibrary);
Finalizers nativeDone;
Finalizers guestDone;
SetFinalizers(host, nativeDone);
SetFinalizers(face, guestDone);
Require(FT_Done_Face(host) == 0);
Require(FT_Done_Face_nid_postfix(face) == 0);
Require(nativeDone.calls == std::vector<int>({1, 2, 3}) && guestDone.calls == nativeDone.calls);
Require(FT_Get_Char_Index_nid_postfix(masked, 'A') == 1);
FT_Face nativeFirst = nullptr;
FT_Face nativeSecond = nullptr;
Require(FT_New_Memory_Face(hostLibrary, font.data(), static_cast<FT_Long>(font.size()), 0, &nativeFirst) == 0);
Require(FT_New_Memory_Face(hostLibrary, font.data(), static_cast<FT_Long>(font.size()), 0, &nativeSecond) == 0);
Finalizers nativeFirstEvents;
Finalizers nativeSecondEvents;
Finalizers guestFirstEvents;
Finalizers guestSecondEvents;
SetFinalizers(nativeFirst, nativeFirstEvents);
SetFinalizers(nativeSecond, nativeSecondEvents);
SetFinalizers(masked, guestFirstEvents);
SetFinalizers(memoryFace, guestSecondEvents);
Require(FT_Done_FreeType(hostLibrary) == 0);
void* independent = nullptr;
GuestFaceRec* independentFace = nullptr;
Require(FT_Init_FreeType_nid_postfix(&independent) == 0);
Require(FT_New_Memory_Face_nid_postfix(independent, font.data(), fontSize, 0, &independentFace) == 0);
Finalizers independentEvents;
SetFinalizers(independentFace, independentEvents);
Require(FT_Done_FreeType_nid_postfix(library) == 0);
Require(guestFirstEvents.calls == nativeFirstEvents.calls && guestSecondEvents.calls == nativeSecondEvents.calls);
Require(guestFirstEvents.calls == nativeDone.calls && independentEvents.calls.empty());
Require(FT_Get_Char_Index_nid_postfix(independentFace, 'A') == 1);
Require(FT_Done_FreeType_nid_postfix(independent) == 0 && independentEvents.calls == nativeDone.calls);
void* empty = nullptr;
Require(FT_Init_FreeType_nid_postfix(&empty) == 0 && FT_Done_FreeType_nid_postfix(empty) == 0);
std::filesystem::current_path(previous);
std::error_code ignored;
std::filesystem::remove_all(root, ignored);
+5 -1
View File
@@ -6,6 +6,10 @@
- [FT_Init_FreeType, FT_New_Face, FT_Set_Pixel_Sizes, FT_Get_Char_Index, FT_Load_Glyph, FT_Render_Glyph](../../../core/libs/prx/libSceFreeType/Export.cpp) (libSceFreeType) - the PS4 library is taken to be FreeType 2 built for LP64: the face, size, charmap, glyph slot, bitmap and outline layouts follow the public headers of the bundled FreeType 2.13.3 with a 64-bit `long`, which matches the offsets the OpenOrbis samples read (`face->glyph` at 0x98, `slot->advance` at 0x80, `slot->bitmap` at 0x98, `slot->bitmap_left` at 0xc0). Which FreeType version and options the console library is built with is unknown, so the behaviour and error codes are those of FreeType 2.13.3. The OpenOrbis font, keyboard, threading, system and SDL2 samples import these six functions
- [FT_New_Memory_Face, FT_Done_Face and FT_Done_FreeType](../../../core/libs/prx/libSceFreeType/Export.cpp) (libSceFreeType) - console signatures, errors and generic-finalizer ordering are unverified; they follow the bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/include/freetype/freetype.h) with LP64 guest arguments. Caller-owned font bytes must remain valid until the face is freed.
### Functional
- [libSceFreeType](../../../core/libs/prx/libSceFreeType/src/GuestObjects.cpp) (libSceFreeType) - the guest reads copies of the bundled FreeType's face, size and glyph slot, updated after each call that changes them. Their private fields (`driver`, `memory`, `stream`, `sizes_list`, `extensions`) are null, and `subglyphs` is null whatever `num_subglyphs` reports. The outline points are copied as 64-bit coordinates; changes the guest makes to them and to the outline flags are written back before `FT_Render_Glyph`, while replacing the outline arrays, or a coordinate the host `FT_Pos` cannot hold, throws. `FT_New_Face` resolves the guest path and reads the whole file into memory; on Windows, where FreeType's `long` is 32 bits, a font of 2 GiB or more throws. Libraries and faces are never freed, since `FT_Done_Face` and `FT_Done_FreeType` are not exported. The bundled FreeType's own functions are kept out of the exports with `--exclude-libs` on Windows and Linux only; on macOS they stay exported, so a guest import of another FreeType function could resolve to them instead of failing at start-up
- [libSceFreeType](../../../core/libs/prx/libSceFreeType/src/GuestObjects.cpp) (libSceFreeType) - private fields (`driver`, `memory`, `stream`, `sizes_list`, `extensions`) and `subglyphs` stay null. Face, size and glyph-slot records are copies. Only edits to outline points and flags are written back before `FT_Render_Glyph`; replacing arrays or exceeding the host `FT_Pos` range throws. `FT_New_Face` reads the whole file into memory and throws for files of 2 GiB or more on Windows. On macOS, native FreeType symbols remain exported and could resolve guest imports without adaptation; Windows and Linux hide them with `--exclude-libs`.
- [FT_New_Memory_Face](../../../core/libs/prx/libSceFreeType/Export.cpp) (libSceFreeType) - on Windows, the host FT_Long is 32 bits, so buffer sizes of 2 GiB or more or below -2 GiB throw before the font buffer is read