diff --git a/core/libs/prx/libSceFreeType/Export.cpp b/core/libs/prx/libSceFreeType/Export.cpp index 9eaae33cae..825bc9b1bb 100644 --- a/core/libs/prx/libSceFreeType/Export.cpp +++ b/core/libs/prx/libSceFreeType/Export.cpp @@ -3,6 +3,8 @@ #include "prx/libc/include/FilesystemError.hpp" #include "prx/libc/include/General.hpp" +#include FT_GASP_H + #include #include #include @@ -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(static_cast(charCode)), loadFlags); diff --git a/core/libs/tests/FreeTypeFontFixture.hpp b/core/libs/tests/FreeTypeFontFixture.hpp index 9458d09a81..b876a19139 100644 --- a/core/libs/tests/FreeTypeFontFixture.hpp +++ b/core/libs/tests/FreeTypeFontFixture.hpp @@ -20,7 +20,8 @@ static void PutAll16(std::vector& out, std::initializer_list for (const int value : values) Put16(out, static_cast(static_cast(value))); } -static std::vector SquareGlyphFont(std::uint32_t codepoint, int width, bool symbolMap = false) { +static std::vector SquareGlyphFont(std::uint32_t codepoint, int width, bool symbolMap = false, + const std::vector* gasp = nullptr) { std::vector head; Put32(head, 0x00010000u); Put32(head, 0x00010000u); @@ -53,11 +54,19 @@ static std::vector SquareGlyphFont(std::uint32_t codepoint, int w const int symbol = static_cast(codepoint + 0xF000); PutAll16(cmap, {4, 32, 0, 4, 4, 1, 0, symbol, 0xFFFF, 0, symbol, 0xFFFF, 1 - symbol, 1, 0, 0}); } - const std::vector*>> tables = { + std::vector*>> 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 font; Put32(font, 0x00010000u); - PutAll16(font, {static_cast(tables.size()), 64, 2, static_cast(tables.size()) * 16 - 64}); + PutAll16(font, {static_cast(tables.size()), static_cast(power * 16), static_cast(selector), + static_cast(tables.size() * 16 - power * 16)}); std::uint32_t offset = 12 + static_cast(tables.size()) * 16; for (const auto& [tag, data] : tables) { font.insert(font.end(), tag, tag + 4); diff --git a/core/libs/tests/GuestFreeType.cpp b/core/libs/tests/GuestFreeType.cpp index bccc9a51c2..a68e64258f 100644 --- a/core/libs/tests/GuestFreeType.cpp +++ b/core/libs/tests/GuestFreeType.cpp @@ -4,6 +4,7 @@ #include #include FT_FREETYPE_H +#include FT_GASP_H #include FT_OUTLINE_H #include @@ -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* table, const std::vector>& 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(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(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> ranges{{0, 0x8003}, {7, 2}, {15, 1}, {31, 0}, {65535, 0xFFFF}}; + for (const unsigned version : {0u, 1u}) { + std::vector 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 shortRange; + PutAll16(shortRange, {1, 1, 48, FT_GASP_DO_GRAY}); + CheckGaspTable(&shortRange, {{48, FT_GASP_DO_GRAY}}, false, false); + for (const std::vector& table : {std::vector{}, {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 static bool ThrowsWith(TCall call, const std::string& prefix) { try { call(); @@ -361,6 +434,7 @@ static void CheckTransforms(const std::vector& 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 font = SquareGlyphFont('A', 700); { std::ofstream file(root / "app0" / "font.ttf", std::ios::binary); diff --git a/docs/dev/debt/libSceFreeType.md b/docs/dev/debt/libSceFreeType.md index d56614d548..fc27092fbb 100644 --- a/docs/dev/debt/libSceFreeType.md +++ b/docs/dev/debt/libSceFreeType.md @@ -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.