Merge pull request #9550 from Ondra97/codex/feat-freetype-size-objects

feat(libSceFreeType): manage multiple size objects
This commit is contained in:
Adrià authored and GitHub committed 2026-10-25 23:49:05 +01:00
commit e82b11daaf
6 files changed
+340 -11

No files matched your search

+62 -4
View File
@@ -6,6 +6,7 @@
#include FT_GASP_H
#include FT_CID_H
#include FT_SIZES_H
#include <algorithm>
#include <bit>
@@ -33,14 +34,17 @@ void FinalizeSlot(void* object) {
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;
auto* rec = static_cast<GuestSizeRec*>(host->generic.data);
auto* face = static_cast<GuestFace*>(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<FT_Face>(object);
auto* face = static_cast<GuestFace*>(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<GuestFace> 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<FT_Size>(size->internal) : nullptr;
const FT_Error error = FT_Activate_Size(host);
if (error == 0) SyncSize(*static_cast<GuestFace*>(host->face->generic.data));
return error;
}
int APS5_VABI FT_Done_Size_nid_postfix(GuestSizeRec* size) {
FT_Size host = size ? static_cast<FT_Size>(size->internal) : nullptr;
auto* owner = host && host->face ? static_cast<GuestFace*>(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);
@@ -13,6 +13,9 @@ int APS5_VABI FT_New_Memory_Face_nid_postfix(FreeType::GuestLibrary* library, co
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_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);
@@ -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<GuestSize> sizes;
std::vector<unsigned char> bytes;
std::vector<GuestBitmapSize> fixedSizes;
std::vector<GuestCharMapRec> 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);
}
@@ -1,6 +1,7 @@
#include "prx/libSceFreeType/include/GuestObjects.hpp"
#include <cstddef>
#include <stdexcept>
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) {
+227
View File
@@ -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 <algorithm>
#include <array>
@@ -1257,7 +1259,232 @@ static void CheckTransforms(const std::vector<unsigned char>& 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);
}
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<void*> sizes;
std::vector<std::array<int, 4>> 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<int>(index);
}
Require(false);
return -2;
}
};
static void NativeMultipleSizeFinalizer(void* object) {
auto* size = static_cast<FT_Size>(object);
auto& events = *static_cast<SizeEvents*>(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<GuestSizeRec*>(object);
auto* face = static_cast<GuestFaceRec*>(size->face);
const auto& owner = *static_cast<GuestFace*>(face->internal);
Require(OwnsSize(owner, size));
if (face->size) Require(OwnsSize(owner, face->size));
SizeMatches(size, static_cast<FT_Size>(size->internal));
auto& events = *static_cast<SizeEvents*>(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<FT_Face>(object);
Require(face->glyph == nullptr && face->size == nullptr);
static_cast<SizeEvents*>(face->generic.data)->calls.push_back({100, -1, 0, 1});
}
static void APS5_VABI GuestMultipleSizeFaceFinalizer(void* object) {
auto* face = static_cast<GuestFaceRec*>(object);
Require(face->glyph == nullptr && face->size == nullptr);
static_cast<SizeEvents*>(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<unsigned char>& 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<FT_Size>(&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}) {
GuestLibrary* 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<FT_Long>(font.size()), faceIndex, &native) == 0);
SizeEvents actual;
SizeEvents expected;
face->generic = {&actual, GuestMultipleSizeFaceFinalizer};
native->generic = {&expected, NativeMultipleSizeFaceFinalizer};
std::vector<GuestSizeRec*> sizes;
std::vector<FT_Size> 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<GuestFace*>(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<FT_Size>(&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 && 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;
}
}
}
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;
+2
View File
@@ -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