mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 16:43:05 -05:00
349 lines
17 KiB
C++
349 lines
17 KiB
C++
#include "prx/libSceFont/include/FontTypes.hpp"
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <initializer_list>
|
|
#include <iterator>
|
|
#include <random>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
#ifdef _WIN32
|
|
#include <io.h>
|
|
#else
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
extern "C" {
|
|
int APS5_VABI sceFontMemoryInit(FontMemory*, void*, std::uint32_t, const FontMemoryInterface*, void*, FontMemoryDestroyFunction, void*);
|
|
int APS5_VABI sceFontCreateLibrary(const FontMemory*, const void*, FontLibrary*);
|
|
int APS5_VABI sceFontDestroyLibrary(FontLibrary*);
|
|
int APS5_VABI sceFontSupportSystemFonts(FontLibrary);
|
|
int APS5_VABI sceFontSupportExternalFonts(FontLibrary, std::uint32_t, std::uint32_t);
|
|
int APS5_VABI sceFontOpenFontSet(FontLibrary, std::uint32_t, std::uint32_t, const FontOpenDetail*, FontHandle*);
|
|
int APS5_VABI sceFontOpenFontMemory(FontLibrary, const void*, std::uint32_t, const FontOpenDetail*, FontHandle*);
|
|
int APS5_VABI sceFontOpenFontInstance(FontHandle, FontHandle, FontHandle*);
|
|
int APS5_VABI sceFontCloseFont(FontHandle);
|
|
int APS5_VABI sceFontSetScalePixel(FontHandle, float, float);
|
|
int APS5_VABI sceFontGetCharGlyphMetrics(FontHandle, std::uint32_t, FontGlyphMetrics*);
|
|
int APS5_VABI sceFontGetHorizontalLayout(FontHandle, FontHorizontalLayout*);
|
|
int APS5_VABI sceFontGetKerning(FontHandle, std::uint32_t, std::uint32_t, FontKerning*);
|
|
int APS5_VABI sceFontCreateRenderer(const FontMemory*, const void*, FontRenderer*);
|
|
int APS5_VABI sceFontDestroyRenderer(FontRenderer*);
|
|
int APS5_VABI sceFontBindRenderer(FontHandle, FontRenderer);
|
|
int APS5_VABI sceFontUnbindRenderer(FontHandle);
|
|
int APS5_VABI sceFontSetupRenderScalePixel(FontHandle, float, float);
|
|
void APS5_VABI sceFontRenderSurfaceInit(FontRenderSurface*, void*, int, int, int, int);
|
|
int APS5_VABI sceFontRenderCharGlyphImageHorizontal(FontHandle, std::uint32_t, FontRenderSurface*, float, float, FontGlyphMetrics*, FontRenderOutput*);
|
|
const void* APS5_VABI sceFontSelectLibraryFt(int);
|
|
const void* APS5_VABI sceFontSelectRendererFt(int);
|
|
}
|
|
|
|
static void Check(bool value, int line) {
|
|
if (!value) {
|
|
std::fprintf(stderr, "System font check failed at line %d\n", line);
|
|
std::abort();
|
|
}
|
|
}
|
|
#define Require(value) Check((value), __LINE__)
|
|
|
|
static int allocations = 0;
|
|
static void* APS5_VABI Allocate(void*, std::uint32_t size) {
|
|
++allocations;
|
|
return std::malloc(size);
|
|
}
|
|
static void APS5_VABI Release(void*, void* pointer) {
|
|
if (pointer) --allocations;
|
|
std::free(pointer);
|
|
}
|
|
|
|
static void Put16(std::vector<unsigned char>& out, std::uint32_t value) {
|
|
out.push_back(static_cast<unsigned char>(value >> 8));
|
|
out.push_back(static_cast<unsigned char>(value));
|
|
}
|
|
|
|
static void Put32(std::vector<unsigned char>& out, std::uint32_t value) {
|
|
Put16(out, value >> 16);
|
|
Put16(out, value & 0xFFFFu);
|
|
}
|
|
|
|
static void PutAll16(std::vector<unsigned char>& out, std::initializer_list<int> values) {
|
|
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) {
|
|
std::vector<unsigned char> head;
|
|
Put32(head, 0x00010000u);
|
|
Put32(head, 0x00010000u);
|
|
Put32(head, 0);
|
|
Put32(head, 0x5F0F3CF5u);
|
|
PutAll16(head, {0x000B, 1000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, width, 800, 0, 8, 2, 0, 0});
|
|
std::vector<unsigned char> hhea;
|
|
Put32(hhea, 0x00010000u);
|
|
PutAll16(hhea, {800, -200, 0, width, 0, 0, width, 1, 0, 0, 0, 0, 0, 0, 0, 2});
|
|
std::vector<unsigned char> maxp;
|
|
Put32(maxp, 0x00010000u);
|
|
PutAll16(maxp, {2, 4, 1, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0});
|
|
std::vector<unsigned char> hmtx;
|
|
PutAll16(hmtx, {width, 0, width, 100});
|
|
std::vector<unsigned char> glyf;
|
|
PutAll16(glyf, {1, 100, 0, width - 100, 700, 3, 0});
|
|
glyf.insert(glyf.end(), {1, 1, 1, 1});
|
|
PutAll16(glyf, {100, 0, width - 200, 0, 0, 700, 0, -700});
|
|
std::vector<unsigned char> loca;
|
|
PutAll16(loca, {0, 0, static_cast<int>(glyf.size() / 2)});
|
|
std::vector<unsigned char> cmap;
|
|
PutAll16(cmap, {0, 1, 3, 1});
|
|
Put32(cmap, 12);
|
|
PutAll16(cmap, {4, 32, 0, 4, 4, 1, 0, static_cast<int>(codepoint), 0xFFFF, 0, static_cast<int>(codepoint), 0xFFFF, 1 - static_cast<int>(codepoint), 1, 0, 0});
|
|
const 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}};
|
|
std::vector<unsigned char> font;
|
|
Put32(font, 0x00010000u);
|
|
PutAll16(font, {static_cast<int>(tables.size()), 64, 2, static_cast<int>(tables.size()) * 16 - 64});
|
|
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);
|
|
Put32(font, 0);
|
|
Put32(font, offset);
|
|
Put32(font, static_cast<std::uint32_t>(data->size()));
|
|
offset += (static_cast<std::uint32_t>(data->size()) + 3u) & ~3u;
|
|
}
|
|
for (const auto& [tag, data] : tables) {
|
|
font.insert(font.end(), data->begin(), data->end());
|
|
font.resize((font.size() + 3u) & ~std::size_t{3});
|
|
}
|
|
return font;
|
|
}
|
|
|
|
static void WriteFile(const std::filesystem::path& path, const std::vector<unsigned char>& bytes) {
|
|
std::ofstream file(path, std::ios::binary);
|
|
file.write(reinterpret_cast<const char*>(bytes.data()), static_cast<std::streamsize>(bytes.size()));
|
|
Require(static_cast<bool>(file));
|
|
}
|
|
|
|
static void SetFontDirectory(const std::filesystem::path& directory) {
|
|
#ifdef _WIN32
|
|
Require(_putenv_s("ANYPS5_SYSTEM_FONTS", directory.string().c_str()) == 0);
|
|
#else
|
|
Require(::setenv("ANYPS5_SYSTEM_FONTS", directory.string().c_str(), 1) == 0);
|
|
#endif
|
|
}
|
|
|
|
template <typename TRun>
|
|
static std::string CaptureStderr(const std::filesystem::path& path, TRun run) {
|
|
std::fflush(stderr);
|
|
#ifdef _WIN32
|
|
const int saved = _dup(2);
|
|
#else
|
|
const int saved = dup(2);
|
|
#endif
|
|
std::FILE* file = std::fopen(path.string().c_str(), "wb");
|
|
Require(saved >= 0 && file != nullptr);
|
|
#ifdef _WIN32
|
|
Require(_dup2(_fileno(file), 2) == 0);
|
|
#else
|
|
Require(dup2(fileno(file), 2) == 2);
|
|
#endif
|
|
run();
|
|
std::fflush(stderr);
|
|
#ifdef _WIN32
|
|
Require(_dup2(saved, 2) == 0);
|
|
_close(saved);
|
|
#else
|
|
Require(dup2(saved, 2) == 2);
|
|
close(saved);
|
|
#endif
|
|
std::fclose(file);
|
|
std::ifstream input(path, std::ios::binary);
|
|
return std::string(std::istreambuf_iterator<char>(input), std::istreambuf_iterator<char>());
|
|
}
|
|
|
|
static std::size_t Occurrences(const std::string& text, const std::string& pattern) {
|
|
std::size_t count = 0;
|
|
for (auto at = text.find(pattern); at != std::string::npos; at = text.find(pattern, at + pattern.size())) ++count;
|
|
return count;
|
|
}
|
|
|
|
static float AdvanceOf(FontHandle font, std::uint32_t code) {
|
|
FontGlyphMetrics metrics{};
|
|
Require(sceFontGetCharGlyphMetrics(font, code, &metrics) == SCE_FONT_OK);
|
|
return metrics.Horizontal.advance;
|
|
}
|
|
|
|
static float SetAdvanceOf(FontLibrary library, std::uint32_t fontSetType, std::uint32_t code) {
|
|
FontHandle set = nullptr;
|
|
Require(sceFontOpenFontSet(library, fontSetType, 1, nullptr, &set) == SCE_FONT_OK);
|
|
Require(sceFontSetScalePixel(set, 100.0f, 100.0f) == SCE_FONT_OK);
|
|
const float advance = AdvanceOf(set, code);
|
|
Require(sceFontCloseFont(set) == SCE_FONT_OK);
|
|
return advance;
|
|
}
|
|
|
|
static int RenderedPixels(FontHandle font, std::uint32_t code, float& advance) {
|
|
std::vector<std::uint8_t> pixels(128 * 128);
|
|
FontRenderSurface surface{};
|
|
sceFontRenderSurfaceInit(&surface, pixels.data(), 128, 1, 128, 128);
|
|
FontGlyphMetrics metrics{};
|
|
FontRenderOutput result{};
|
|
const int rc = sceFontRenderCharGlyphImageHorizontal(font, code, &surface, 10.0f, 100.0f, &metrics, &result);
|
|
advance = metrics.Horizontal.advance;
|
|
if (rc != SCE_FONT_OK) return rc;
|
|
int lit = 0;
|
|
for (const std::uint8_t pixel : pixels) lit += pixel != 0 ? 1 : 0;
|
|
return lit;
|
|
}
|
|
|
|
int main() {
|
|
constexpr std::uint32_t EuropeanLight = 0x180700C3u;
|
|
constexpr std::uint32_t EuropeanBold = 0x180700C7u;
|
|
constexpr std::uint32_t EuropeanItalic = 0x18170044u;
|
|
constexpr std::uint32_t ThaiMedium = 0x18071055u;
|
|
constexpr std::uint32_t VietnameseBold = 0x18070057u;
|
|
constexpr std::uint32_t JapaneseJg2Light = 0x1A0835D3u;
|
|
constexpr std::uint32_t ChineseGb = 0x180CB0D4u;
|
|
const std::filesystem::path root = std::filesystem::temp_directory_path() / ("anyps5_guest_system_font-" + std::to_string(std::random_device{}()));
|
|
std::filesystem::remove_all(root);
|
|
const std::filesystem::path empty = root / "empty";
|
|
const std::filesystem::path fonts = root / "fonts";
|
|
std::filesystem::create_directories(empty);
|
|
std::filesystem::create_directories(fonts);
|
|
WriteFile(fonts / "SST-Bold.otf", SquareGlyphFont('A', 700));
|
|
WriteFile(fonts / "NotoSans-Bold.ttf", SquareGlyphFont('A', 300));
|
|
WriteFile(fonts / "NotoSans-Light.ttf", SquareGlyphFont('A', 500));
|
|
WriteFile(fonts / "SST-Italic.otf", SquareGlyphFont('A', 520));
|
|
WriteFile(fonts / "NotoSansThai-Medium.ttf", SquareGlyphFont(0x0E01, 540));
|
|
WriteFile(fonts / "SSTVietnamese-Bold.otf", SquareGlyphFont(0x1EA0, 560));
|
|
WriteFile(fonts / "NotoSansCJK-Light.ttc", SquareGlyphFont(0x3042, 580));
|
|
|
|
const FontMemoryInterface iface{Allocate, Release, nullptr, nullptr, nullptr, nullptr};
|
|
FontMemory memory{};
|
|
Require(sceFontMemoryInit(&memory, nullptr, 0, &iface, nullptr, nullptr, nullptr) == SCE_FONT_OK);
|
|
FontLibrary library = nullptr;
|
|
Require(sceFontCreateLibrary(&memory, sceFontSelectLibraryFt(0), &library) == SCE_FONT_OK);
|
|
Require(sceFontSupportSystemFonts(library) == SCE_FONT_OK);
|
|
|
|
FontHandle font = nullptr;
|
|
SetFontDirectory(empty);
|
|
const std::string warnings = CaptureStderr(root / "stderr.txt", [&] {
|
|
Require(sceFontOpenFontSet(library, EuropeanBold, 1, nullptr, &font) == SCE_FONT_ERROR_FONT_OPEN_FAILED && font == nullptr);
|
|
Require(sceFontOpenFontSet(library, EuropeanLight, 1, nullptr, &font) == SCE_FONT_ERROR_FONT_OPEN_FAILED && font == nullptr);
|
|
Require(sceFontOpenFontSet(library, 0x18070046u, 1, nullptr, &font) == SCE_FONT_ERROR_NO_SUPPORT_FONTSET && font == nullptr);
|
|
});
|
|
Require(Occurrences(warnings, "[libSceFont] no file for system font set") == 1);
|
|
Require(Occurrences(warnings, "0x180700C7 in " + empty.string()) == 1 && Occurrences(warnings, "ANYPS5_SYSTEM_FONTS") == 1);
|
|
SetFontDirectory(root / "missing");
|
|
bool threw = false;
|
|
try {
|
|
sceFontOpenFontSet(library, EuropeanBold, 1, nullptr, &font);
|
|
} catch (const std::runtime_error&) {
|
|
threw = true;
|
|
}
|
|
Require(threw);
|
|
|
|
SetFontDirectory(fonts);
|
|
const int beforeOpen = allocations;
|
|
Require(sceFontOpenFontSet(library, EuropeanBold, 1, nullptr, &font) == SCE_FONT_OK && font != nullptr);
|
|
Require(allocations - beforeOpen == 3);
|
|
Require(sceFontSetScalePixel(font, 100.0f, 100.0f) == SCE_FONT_OK);
|
|
Require(AdvanceOf(font, 'A') == 70.0f);
|
|
FontGlyphMetrics metrics{};
|
|
Require(sceFontGetCharGlyphMetrics(font, 'A', &metrics) == SCE_FONT_OK);
|
|
Require(metrics.width == 50.0f && metrics.height == 70.0f && metrics.Horizontal.bearingX == 10.0f && metrics.Horizontal.bearingY == 70.0f);
|
|
Require(metrics.Vertical.bearingX == -25.0f && metrics.Vertical.bearingY == 15.0f && metrics.Vertical.advance == 100.0f);
|
|
Require(sceFontGetCharGlyphMetrics(font, 'B', &metrics) == SCE_FONT_ERROR_NO_SUPPORT_GLYPH);
|
|
FontHorizontalLayout layout{};
|
|
Require(sceFontGetHorizontalLayout(font, &layout) == SCE_FONT_OK && layout.baselineOffset > 0.0f && layout.lineAdvance >= layout.baselineOffset);
|
|
|
|
FontHandle again = nullptr;
|
|
Require(sceFontOpenFontSet(library, EuropeanBold, 2, nullptr, &again) == SCE_FONT_OK && again != font);
|
|
FontHandle instance = nullptr;
|
|
Require(sceFontOpenFontInstance(font, nullptr, &instance) == SCE_FONT_OK && instance != nullptr);
|
|
Require(sceFontGetHorizontalLayout(instance, &layout) == SCE_FONT_OK && layout.baselineOffset > 0.0f);
|
|
|
|
const std::vector<std::pair<std::uint32_t, std::pair<std::uint32_t, float>>> expected = {
|
|
{EuropeanLight, {'A', 50.0f}}, {EuropeanItalic, {'A', 52.0f}}, {ThaiMedium, {0x0E01, 54.0f}},
|
|
{VietnameseBold, {0x1EA0, 56.0f}}, {JapaneseJg2Light, {0x3042, 58.0f}}};
|
|
for (const auto& [type, glyph] : expected) {
|
|
FontHandle set = nullptr;
|
|
Require(sceFontOpenFontSet(library, type, 1, nullptr, &set) == SCE_FONT_OK);
|
|
Require(sceFontSetScalePixel(set, 100.0f, 100.0f) == SCE_FONT_OK);
|
|
Require(AdvanceOf(set, glyph.first) == glyph.second);
|
|
Require(sceFontCloseFont(set) == SCE_FONT_OK);
|
|
}
|
|
Require(sceFontCloseFont(again) == SCE_FONT_OK);
|
|
FontHandle missing = nullptr;
|
|
Require(sceFontOpenFontSet(library, ChineseGb, 1, nullptr, &missing) == SCE_FONT_ERROR_FONT_OPEN_FAILED && missing == nullptr);
|
|
|
|
const auto substituteFonts = root / "substitute-fonts";
|
|
std::filesystem::create_directories(substituteFonts);
|
|
WriteFile(substituteFonts / "NotoSans-Bold.ttf", SquareGlyphFont('A', 300));
|
|
SetFontDirectory(substituteFonts);
|
|
FontHandle substitute = nullptr;
|
|
Require(sceFontOpenFontSet(library, EuropeanBold, 3, nullptr, &substitute) == SCE_FONT_OK);
|
|
Require(sceFontSetScalePixel(substitute, 100.0f, 100.0f) == SCE_FONT_OK);
|
|
Require(AdvanceOf(substitute, 'A') == 30.0f);
|
|
Require(AdvanceOf(font, 'A') == 70.0f);
|
|
|
|
Require(sceFontSupportExternalFonts(library, 2, 0x52) == SCE_FONT_OK);
|
|
const std::vector<unsigned char> external = SquareGlyphFont('A', 900);
|
|
FontHandle memoryFont = nullptr;
|
|
Require(sceFontOpenFontMemory(library, external.data(), static_cast<std::uint32_t>(external.size()), nullptr, &memoryFont) == SCE_FONT_OK);
|
|
Require(sceFontSetScalePixel(memoryFont, 100.0f, 100.0f) == SCE_FONT_OK);
|
|
Require(AdvanceOf(memoryFont, 'A') == 90.0f);
|
|
Require(sceFontGetHorizontalLayout(memoryFont, &layout) == SCE_FONT_OK);
|
|
|
|
constexpr std::uint32_t EuropeanJpAr = 0x180704C4u;
|
|
constexpr std::uint32_t EuropeanJpCjk = 0x1807A444u;
|
|
constexpr std::uint32_t EuropeanGbCjk = 0x1807AC44u;
|
|
constexpr std::uint32_t European = 0x18070044u;
|
|
const auto fallbackFonts = root / "fallback-fonts";
|
|
std::filesystem::create_directories(fallbackFonts);
|
|
WriteFile(fallbackFonts / "SST-Roman.otf", SquareGlyphFont('A', 600));
|
|
WriteFile(fallbackFonts / "SSTJpPro-Regular.otf", SquareGlyphFont(0x4E00, 630));
|
|
WriteFile(fallbackFonts / "DFHEI5-SONY.ttf", SquareGlyphFont(0x4E00, 640));
|
|
WriteFile(fallbackFonts / "SCEPS4Yoongd-Medium.otf", SquareGlyphFont(0xAC00, 650));
|
|
WriteFile(fallbackFonts / "NotoSansArabic-Regular.ttf", SquareGlyphFont(0x0627, 660));
|
|
SetFontDirectory(fallbackFonts);
|
|
Require(SetAdvanceOf(library, EuropeanJpAr, 'A') == 60.0f);
|
|
Require(SetAdvanceOf(library, EuropeanJpAr, 0x4E00) == 63.0f);
|
|
Require(SetAdvanceOf(library, EuropeanJpAr, 0x0627) == 66.0f);
|
|
Require(SetAdvanceOf(library, EuropeanJpCjk, 0x4E00) == 63.0f);
|
|
Require(SetAdvanceOf(library, EuropeanJpCjk, 0xAC00) == 65.0f);
|
|
Require(SetAdvanceOf(library, EuropeanGbCjk, 0x4E00) == 64.0f);
|
|
WriteFile(fallbackFonts / "NotoSans-Regular.ttf", SquareGlyphFont(0x1EA0, 670));
|
|
Require(SetAdvanceOf(library, 0x18070454u, 0x1EA0) == 67.0f);
|
|
FontHandle plain = nullptr;
|
|
Require(sceFontOpenFontSet(library, European, 1, nullptr, &plain) == SCE_FONT_OK);
|
|
Require(sceFontGetCharGlyphMetrics(plain, 0x4E00, &metrics) == SCE_FONT_ERROR_NO_SUPPORT_GLYPH);
|
|
Require(sceFontCloseFont(plain) == SCE_FONT_OK);
|
|
|
|
FontHandle mixed = nullptr;
|
|
Require(sceFontOpenFontSet(library, EuropeanJpAr, 1, nullptr, &mixed) == SCE_FONT_OK);
|
|
FontHandle mixedInstance = nullptr;
|
|
Require(sceFontOpenFontInstance(mixed, nullptr, &mixedInstance) == SCE_FONT_OK);
|
|
Require(sceFontSetScalePixel(mixedInstance, 100.0f, 100.0f) == SCE_FONT_OK);
|
|
Require(AdvanceOf(mixedInstance, 0x0627) == 66.0f);
|
|
FontKerning kerning{1.0f, 1.0f, 1.0f, 1.0f};
|
|
Require(sceFontGetKerning(mixed, 'A', 0x4E00, &kerning) == SCE_FONT_OK && kerning.offsetX == 0.0f);
|
|
FontRenderer renderer = nullptr;
|
|
Require(sceFontCreateRenderer(&memory, sceFontSelectRendererFt(0), &renderer) == SCE_FONT_OK);
|
|
Require(sceFontBindRenderer(mixed, renderer) == SCE_FONT_OK);
|
|
Require(sceFontSetupRenderScalePixel(mixed, 100.0f, 100.0f) == SCE_FONT_OK);
|
|
float advance = 0.0f;
|
|
Require(RenderedPixels(mixed, 'A', advance) > 0 && advance == 60.0f);
|
|
Require(RenderedPixels(mixed, 0x4E00, advance) > 0 && advance == 63.0f);
|
|
Require(RenderedPixels(mixed, 0x0627, advance) > 0 && advance == 66.0f);
|
|
Require(RenderedPixels(mixed, 0xAC00, advance) == SCE_FONT_ERROR_NO_SUPPORT_GLYPH);
|
|
Require(sceFontUnbindRenderer(mixed) == SCE_FONT_OK);
|
|
Require(sceFontDestroyRenderer(&renderer) == SCE_FONT_OK);
|
|
|
|
for (FontHandle handle : {font, instance, substitute, memoryFont, mixed, mixedInstance}) Require(sceFontCloseFont(handle) == SCE_FONT_OK);
|
|
Require(sceFontDestroyLibrary(&library) == SCE_FONT_OK);
|
|
Require(allocations == 0);
|
|
std::filesystem::remove_all(root);
|
|
}
|