feat(libSceFreeType): implement FT_Get_Gasp

This commit is contained in:
Ondra97 committed 2026-10-23 07:44:17 +02:00
1 parent 0db47b263d
commit b13deef0db
4 files changed
+95 -3

No files matched your search

+7
View File
@@ -3,6 +3,8 @@
#include "prx/libc/include/FilesystemError.hpp"
#include "prx/libc/include/General.hpp"
#include FT_GASP_H
#include <algorithm>
#include <bit>
#include <cstdint>
@@ -186,6 +188,11 @@ int APS5_VABI FT_Load_Glyph_nid_postfix(GuestFaceRec* face, std::uint32_t glyph_
return error;
}
int APS5_VABI FT_Get_Gasp_nid_postfix(GuestFaceRec* face, std::uint32_t ppem) {
const GuestFace* owner = FaceOf(face);
return FT_Get_Gasp(owner ? owner->host : nullptr, ppem);
}
int APS5_VABI FT_Load_Char_nid_postfix(GuestFaceRec* face, std::uint64_t charCode, std::int32_t loadFlags) {
GuestFace* owner = FaceOf(face);
const FT_Error error = FT_Load_Char(owner ? owner->host : nullptr, static_cast<FT_ULong>(static_cast<std::uint32_t>(charCode)), loadFlags);
+12 -3
View File
@@ -20,7 +20,8 @@ static void PutAll16(std::vector<unsigned char>& out, std::initializer_list<int>
for (const int value : values) Put16(out, static_cast<std::uint32_t>(static_cast<std::uint16_t>(value)));
}
static std::vector<unsigned char> SquareGlyphFont(std::uint32_t codepoint, int width, bool symbolMap = false) {
static std::vector<unsigned char> SquareGlyphFont(std::uint32_t codepoint, int width, bool symbolMap = false,
const std::vector<unsigned char>* gasp = nullptr) {
std::vector<unsigned char> head;
Put32(head, 0x00010000u);
Put32(head, 0x00010000u);
@@ -53,11 +54,19 @@ static std::vector<unsigned char> SquareGlyphFont(std::uint32_t codepoint, int w
const int symbol = static_cast<int>(codepoint + 0xF000);
PutAll16(cmap, {4, 32, 0, 4, 4, 1, 0, symbol, 0xFFFF, 0, symbol, 0xFFFF, 1 - symbol, 1, 0, 0});
}
const std::vector<std::pair<const char*, const std::vector<unsigned char>*>> tables = {
std::vector<std::pair<const char*, const std::vector<unsigned char>*>> tables = {
{"cmap", &cmap}, {"glyf", &glyf}, {"head", &head}, {"hhea", &hhea}, {"hmtx", &hmtx}, {"loca", &loca}, {"maxp", &maxp}};
if (gasp) tables.insert(tables.begin() + 1, {"gasp", gasp});
unsigned power = 1;
unsigned selector = 0;
while (power <= tables.size() / 2) {
power *= 2;
++selector;
}
std::vector<unsigned char> font;
Put32(font, 0x00010000u);
PutAll16(font, {static_cast<int>(tables.size()), 64, 2, static_cast<int>(tables.size()) * 16 - 64});
PutAll16(font, {static_cast<int>(tables.size()), static_cast<int>(power * 16), static_cast<int>(selector),
static_cast<int>(tables.size() * 16 - power * 16)});
std::uint32_t offset = 12 + static_cast<std::uint32_t>(tables.size()) * 16;
for (const auto& [tag, data] : tables) {
font.insert(font.end(), tag, tag + 4);
+74
View File
@@ -4,6 +4,7 @@
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GASP_H
#include FT_OUTLINE_H
#include <algorithm>
@@ -28,6 +29,7 @@ extern "C" {
void APS5_VABI FT_Set_Transform_nid_postfix(GuestFaceRec*, const GuestMatrix*, const GuestVector*);
int APS5_VABI FT_Init_FreeType_nid_postfix(void**);
int APS5_VABI FT_Done_FreeType_nid_postfix(void*);
int APS5_VABI FT_Get_Gasp_nid_postfix(GuestFaceRec*, std::uint32_t);
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*);
@@ -47,6 +49,77 @@ static void Check(bool value, int line) {
}
#define Require(value) Check((value), __LINE__)
static void CheckGaspTable(const std::vector<unsigned char>* table, const std::vector<std::pair<unsigned, int>>& ranges,
bool versionZero, bool render) {
const auto font = SquareGlyphFont('A', 700, false, table);
void* library = nullptr;
GuestFaceRec* face = nullptr;
FT_Library nativeLibrary = nullptr;
FT_Face native = nullptr;
Require(FT_Init_FreeType_nid_postfix(&library) == 0 && FT_Init_FreeType(&nativeLibrary) == 0);
Require(FT_New_Memory_Face_nid_postfix(library, font.data(), font.size(), 0, &face) == 0);
Require(FT_New_Memory_Face(nativeLibrary, font.data(), static_cast<FT_Long>(font.size()), 0, &native) == 0);
auto* slot = face->glyph;
const auto glyphIndex = slot->glyph_index;
const auto bitmap = slot->bitmap.buffer;
const auto points = slot->outline.points;
for (std::uint32_t ppem = 0; ppem <= 65536; ++ppem) {
int expected = FT_GASP_NO_TABLE;
for (const auto& [maximum, flags] : ranges) {
if (ppem > maximum) continue;
expected = versionZero ? flags & 3 : flags;
break;
}
Require(FT_Get_Gasp_nid_postfix(face, ppem) == expected);
Require(FT_Get_Gasp(native, ppem) == expected);
}
Require(FT_Get_Gasp_nid_postfix(face, UINT32_MAX) == FT_GASP_NO_TABLE);
Require(face->glyph == slot && slot->glyph_index == glyphIndex && slot->bitmap.buffer == bitmap && slot->outline.points == points);
if (render) {
for (const unsigned ppem : {7u, 8u, 15u, 16u, 31u, 32u, 64u}) {
const int flags = FT_Get_Gasp_nid_postfix(face, ppem);
const int load = flags & FT_GASP_DO_GRIDFIT ? FT_LOAD_DEFAULT : FT_LOAD_NO_HINTING;
const auto mode = flags & FT_GASP_DO_GRAY ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO;
Require(FT_Set_Pixel_Sizes_nid_postfix(face, 0, ppem) == 0 && FT_Set_Pixel_Sizes(native, 0, ppem) == 0);
Require(FT_Load_Char_nid_postfix(face, 'A', load) == 0 && FT_Load_Char(native, 'A', load) == 0);
Require(FT_Render_Glyph_nid_postfix(face->glyph, mode) == 0 && FT_Render_Glyph(native->glyph, mode) == 0);
const auto& actual = face->glyph->bitmap;
const auto& expected = native->glyph->bitmap;
Require(actual.width > 0 && actual.rows > 0);
Require(actual.width == expected.width && actual.rows == expected.rows && actual.pitch == expected.pitch);
Require(actual.pixel_mode == expected.pixel_mode && actual.num_grays == expected.num_grays);
Require(face->glyph->bitmap_left == native->glyph->bitmap_left && face->glyph->bitmap_top == native->glyph->bitmap_top);
Require(std::memcmp(actual.buffer, expected.buffer, static_cast<std::size_t>(actual.rows) * std::abs(actual.pitch)) == 0);
}
}
Require(FT_Done_Face_nid_postfix(face) == 0 && FT_Done_Face(native) == 0);
Require(FT_Done_FreeType_nid_postfix(library) == 0 && FT_Done_FreeType(nativeLibrary) == 0);
}
static void CheckGasp() {
Require(FT_Get_Gasp_nid_postfix(nullptr, 0) == FT_GASP_NO_TABLE);
Require(FT_Get_Gasp_nid_postfix(nullptr, UINT32_MAX) == FT_GASP_NO_TABLE);
CheckGaspTable(nullptr, {}, false, false);
const std::vector<std::pair<unsigned, int>> ranges{{0, 0x8003}, {7, 2}, {15, 1}, {31, 0}, {65535, 0xFFFF}};
for (const unsigned version : {0u, 1u}) {
std::vector<unsigned char> table;
Put16(table, version);
Put16(table, ranges.size());
for (const auto& [maximum, flags] : ranges) {
Put16(table, maximum);
Put16(table, flags);
}
CheckGaspTable(&table, ranges, version == 0, true);
}
std::vector<unsigned char> shortRange;
PutAll16(shortRange, {1, 1, 48, FT_GASP_DO_GRAY});
CheckGaspTable(&shortRange, {{48, FT_GASP_DO_GRAY}}, false, false);
for (const std::vector<unsigned char>& table : {std::vector<unsigned char>{}, {0, 1}, {0, 1, 0, 0}, {0, 2, 0, 1, 0, 10, 0, 3}}) {
CheckGaspTable(&table, {}, false, false);
}
std::puts("FreeType gasp: 8 fonts, 524304 queries, null faces and 14 rendered glyphs passed");
}
template <typename TCall> static bool ThrowsWith(TCall call, const std::string& prefix) {
try {
call();
@@ -361,6 +434,7 @@ static void CheckTransforms(const std::vector<unsigned char>& font) {
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");
CheckGasp();
const std::vector<unsigned char> font = SquareGlyphFont('A', 700);
{
std::ofstream file(root / "app0" / "font.ttf", std::ios::binary);
+2
View File
@@ -22,3 +22,5 @@
- [FT_Outline_Get_Orientation](../../../core/libs/prx/libSceFreeType/Export.cpp) (libSceFreeType) - console ABI/version and large-outline equivalence are unverified. The adapter follows bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftoutln.c) with the existing unsigned guest counts and an LP64 area accumulator, including on Windows.
- [FT_Set_Char_Size](../../../core/libs/prx/libSceFreeType/Export.cpp) (libSceFreeType) - console ABI, FreeType version and error compatibility are unverified. The name and NID `xuY99Qcb108` are in the [PS5 3.20 inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceFreeTypeOt.c); the LP64 signature and behavior follow bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftobjs.c), not a console measurement. After applying its defaults and minimum-size clamp, requests whose `dimension * dpi + 36` exceeds the host's signed `FT_Long` range throw before the native call. This is a 32-bit limit on Windows; larger console requests remain unsupported.
- [FT_Get_Gasp](../../../core/libs/prx/libSceFreeType/Export.cpp) (libSceFreeType) - console FreeType version and query-result compatibility are unverified. The guest face adapter follows bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftgasp.c) and its existing LP64 face bridge.