mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2026-04-29 23:41:19 -06:00
1983 lines
76 KiB
C++
1983 lines
76 KiB
C++
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include "font_internal.h"
|
|
|
|
#include <array>
|
|
|
|
#include <ft2build.h>
|
|
#include FT_FREETYPE_H
|
|
#include FT_OUTLINE_H
|
|
#include FT_TRUETYPE_TABLES_H
|
|
|
|
#include "core/libraries/font/fontft_internal.h"
|
|
|
|
namespace Libraries::Font::Internal {
|
|
|
|
Core::FileSys::MntPoints* g_mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
|
|
|
using Libraries::Font::OrbisFontGenerateGlyphParams;
|
|
using Libraries::Font::OrbisFontGlyph;
|
|
using Libraries::Font::OrbisFontGlyphMetrics;
|
|
using Libraries::Font::OrbisFontGlyphOpaque;
|
|
using Libraries::Font::OrbisFontGlyphOutline;
|
|
using Libraries::Font::OrbisFontGlyphOutlinePoint;
|
|
using Libraries::Font::OrbisFontMem;
|
|
using Libraries::Font::OrbisFontStyleFrame;
|
|
|
|
std::unordered_map<Libraries::Font::OrbisFontHandle, FontState> g_font_state;
|
|
|
|
std::unordered_map<Libraries::Font::OrbisFontLib, LibraryState> g_library_state;
|
|
|
|
std::unordered_map<Libraries::Font::OrbisFontRenderSurface*,
|
|
const Libraries::Font::OrbisFontStyleFrame*>
|
|
g_style_for_surface;
|
|
|
|
void* g_allocator_vtbl_stub[4] = {};
|
|
std::uint8_t g_sys_driver_stub{};
|
|
std::uint8_t g_fontset_registry_stub{};
|
|
std::uint8_t g_sysfonts_ctx_stub{};
|
|
std::uint8_t g_external_fonts_ctx_stub{};
|
|
u32 g_device_cache_stub{};
|
|
|
|
bool HasSfntTables(const std::vector<unsigned char>& bytes);
|
|
FontState* TryGetState(Libraries::Font::OrbisFontHandle h);
|
|
std::string ReportSystemFaceRequest(FontState& st, Libraries::Font::OrbisFontHandle handle,
|
|
bool& attached_out);
|
|
|
|
namespace {
|
|
|
|
static FT_Library GetFreeTypeLibrary() {
|
|
static std::once_flag once;
|
|
static FT_Library lib = nullptr;
|
|
std::call_once(once, [] {
|
|
FT_Library created = nullptr;
|
|
if (FT_Init_FreeType(&created) != 0) {
|
|
created = nullptr;
|
|
}
|
|
lib = created;
|
|
});
|
|
return lib;
|
|
}
|
|
|
|
static constexpr FT_Int32 kFtLoadFlagsBase =
|
|
static_cast<FT_Int32>(FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP | FT_LOAD_VERTICAL_LAYOUT);
|
|
|
|
static constexpr FT_Int32 kFtLoadFlagsRender =
|
|
static_cast<FT_Int32>(kFtLoadFlagsBase | FT_LOAD_RENDER);
|
|
|
|
} // namespace
|
|
|
|
FT_Face CreateFreeTypeFaceFromBytes(const unsigned char* data, std::size_t size,
|
|
u32 subfont_index) {
|
|
if (!data || size == 0) {
|
|
return nullptr;
|
|
}
|
|
FT_Library lib = GetFreeTypeLibrary();
|
|
if (!lib) {
|
|
return nullptr;
|
|
}
|
|
FT_Face face = nullptr;
|
|
if (FT_New_Memory_Face(lib, reinterpret_cast<const FT_Byte*>(data), static_cast<FT_Long>(size),
|
|
static_cast<FT_Long>(subfont_index), &face) != 0) {
|
|
return nullptr;
|
|
}
|
|
(void)FT_Select_Charmap(face, FT_ENCODING_UNICODE);
|
|
return face;
|
|
}
|
|
|
|
void DestroyFreeTypeFace(FT_Face& face) {
|
|
if (!face) {
|
|
return;
|
|
}
|
|
FT_Done_Face(face);
|
|
face = nullptr;
|
|
}
|
|
|
|
namespace {
|
|
|
|
static std::optional<std::filesystem::path> ResolveKnownSysFontAlias(
|
|
const std::filesystem::path& sysfonts_dir, std::string_view ps4_filename) {
|
|
const auto resolve_existing =
|
|
[&](std::string_view filename) -> std::optional<std::filesystem::path> {
|
|
const std::filesystem::path file_path{std::string(filename)};
|
|
std::error_code ec;
|
|
{
|
|
const auto candidate = sysfonts_dir / file_path;
|
|
if (std::filesystem::exists(candidate, ec)) {
|
|
return candidate;
|
|
}
|
|
}
|
|
{
|
|
const auto candidate = sysfonts_dir / "font" / file_path;
|
|
if (std::filesystem::exists(candidate, ec)) {
|
|
return candidate;
|
|
}
|
|
}
|
|
{
|
|
const auto candidate = sysfonts_dir / "font2" / file_path;
|
|
if (std::filesystem::exists(candidate, ec)) {
|
|
return candidate;
|
|
}
|
|
}
|
|
return std::nullopt;
|
|
};
|
|
|
|
static constexpr std::array<std::pair<std::string_view, std::string_view>, 41> kAliases = {{
|
|
{"SST-EU-ROMAN-L.OTF", "SST-Light.otf"},
|
|
{"SST-EU-ROMAN.OTF", "SST-Roman.otf"},
|
|
{"SST-EU-ROMAN-M.OTF", "SST-Medium.otf"},
|
|
{"SST-EU-ROMAN-R.OTF", "SST-Roman.otf"},
|
|
{"SST-EU-ROMAN-I.OTF", "SST-Italic.otf"},
|
|
{"SST-EU-ROMAN-B.OTF", "SST-Bold.otf"},
|
|
{"SST-EU-ROMAN-BI.OTF", "SST-BoldItalic.otf"},
|
|
{"SST-ITALIC-L.OTF", "SST-LightItalic.otf"},
|
|
{"SST-ITALIC-R.OTF", "SST-Italic.otf"},
|
|
{"SST-ITALIC-M.OTF", "SST-MediumItalic.otf"},
|
|
{"SST-ITALIC-B.OTF", "SST-BoldItalic.otf"},
|
|
{"SST-TYPEWRITER-R.OTF", "SSTTypewriter-Roman.otf"},
|
|
{"SST-TYPEWRITER-B.OTF", "SSTTypewriter-Bd.otf"},
|
|
{"SST-JPPRO-R.OTF", "SSTJpPro-Regular.otf"},
|
|
{"SST-JPPRO-B.OTF", "SSTJpPro-Bold.otf"},
|
|
{"SST-CNGB-HEI-R.TTF", "DFHEI5-SONY.ttf"},
|
|
{"SST-ARIB-STD-B24-R.TTF", "SSTAribStdB24-Regular.ttf"},
|
|
{"SST-ARABIC-R.OTF", "SSTArabic-Roman.otf"},
|
|
{"SST-ARABIC-L.OTF", "SSTArabic-Light.otf"},
|
|
{"SST-ARABIC-M.OTF", "SSTArabic-Medium.otf"},
|
|
{"SST-ARABIC-B.OTF", "SSTArabic-Bold.otf"},
|
|
{"SCE-EXT-HANGUL-L.OTF", "SCEPS4Yoongd-Light.otf"},
|
|
{"SCE-EXT-HANGUL-R.OTF", "SCEPS4Yoongd-Medium.otf"},
|
|
{"SCE-EXT-HANGUL-B.OTF", "SCEPS4Yoongd-Bold.otf"},
|
|
{"SSTCC-SERIF-MONO.OTF", "e046323ms.ttf"},
|
|
{"SSTCC-SERIF.OTF", "e046323ts.ttf"},
|
|
{"SSTCC-SANSSERIF-MONO.OTF", "n023055ms.ttf"},
|
|
{"SSTCC-SANSSERIF.OTF", "n023055ts.ttf"},
|
|
{"SSTCC-CUSUAL.OTF", "d013013ds.ttf"},
|
|
{"SSTCC-CURSIVE.OTF", "k006004ds.ttf"},
|
|
{"SSTCC-SMALLCAPITAL.OTF", "c041056ts.ttf"},
|
|
{"SCE-JP-CATTLEYA-L.OTF", "SCE-RDC-R-JPN.otf"},
|
|
{"SCE-JP-CATTLEYA-B.OTF", "SCE-RDC-B-JPN.otf"},
|
|
{"SST-THAI-L.OTF", "SSTThai-Light.otf"},
|
|
{"SST-THAI-R.OTF", "SSTThai-Roman.otf"},
|
|
{"SST-THAI-M.OTF", "SSTThai-Medium.otf"},
|
|
{"SST-THAI-B.OTF", "SSTThai-Bold.otf"},
|
|
{"SST-VIETNAMESE-L.OTF", "SSTVietnamese-Light.otf"},
|
|
{"SST-VIETNAMESE-R.OTF", "SSTVietnamese-Roman.otf"},
|
|
{"SST-VIETNAMESE-M.OTF", "SSTVietnamese-Medium.otf"},
|
|
{"SST-VIETNAMESE-B.OTF", "SSTVietnamese-Bold.otf"},
|
|
}};
|
|
|
|
for (const auto& [from, to] : kAliases) {
|
|
if (ps4_filename == from) {
|
|
return resolve_existing(to);
|
|
}
|
|
if (ps4_filename == to) {
|
|
if (auto reverse = resolve_existing(from)) {
|
|
return reverse;
|
|
}
|
|
}
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
static std::mutex g_sysfont_file_cache_mutex;
|
|
static std::unordered_map<std::string, std::shared_ptr<std::vector<unsigned char>>>
|
|
g_sysfont_file_cache;
|
|
|
|
static std::shared_ptr<std::vector<unsigned char>> GetCachedFontBytes(
|
|
const std::filesystem::path& host_path) {
|
|
const std::string key = host_path.string();
|
|
{
|
|
std::scoped_lock lock(g_sysfont_file_cache_mutex);
|
|
if (auto it = g_sysfont_file_cache.find(key); it != g_sysfont_file_cache.end()) {
|
|
return it->second;
|
|
}
|
|
}
|
|
|
|
std::vector<unsigned char> bytes;
|
|
if (!LoadGuestFileBytes(host_path, bytes) || bytes.empty()) {
|
|
return {};
|
|
}
|
|
|
|
auto shared = std::make_shared<std::vector<unsigned char>>(std::move(bytes));
|
|
{
|
|
std::scoped_lock lock(g_sysfont_file_cache_mutex);
|
|
g_sysfont_file_cache.emplace(key, shared);
|
|
}
|
|
return shared;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
FontState::~FontState() {
|
|
DestroyFreeTypeFace(ext_ft_face);
|
|
for (auto& fb : system_fallback_faces) {
|
|
DestroyFreeTypeFace(fb.ft_face);
|
|
}
|
|
}
|
|
|
|
std::mutex g_generated_glyph_mutex;
|
|
std::unordered_set<OrbisFontGlyph> g_generated_glyphs;
|
|
|
|
void TrackGeneratedGlyph(OrbisFontGlyph glyph) {
|
|
std::scoped_lock lock(g_generated_glyph_mutex);
|
|
g_generated_glyphs.insert(glyph);
|
|
}
|
|
|
|
bool ForgetGeneratedGlyph(OrbisFontGlyph glyph) {
|
|
std::scoped_lock lock(g_generated_glyph_mutex);
|
|
return g_generated_glyphs.erase(glyph) > 0;
|
|
}
|
|
|
|
GeneratedGlyph* TryGetGeneratedGlyph(OrbisFontGlyph glyph) {
|
|
if (!glyph || glyph->magic != 0x0F03) {
|
|
return nullptr;
|
|
}
|
|
std::scoped_lock lock(g_generated_glyph_mutex);
|
|
if (g_generated_glyphs.find(glyph) == g_generated_glyphs.end()) {
|
|
return nullptr;
|
|
}
|
|
return reinterpret_cast<GeneratedGlyph*>(glyph);
|
|
}
|
|
|
|
void PopulateGlyphMetricVariants(GeneratedGlyph& gg) {
|
|
if (gg.metrics_initialized) {
|
|
return;
|
|
}
|
|
gg.metrics_horizontal.width = gg.metrics.width;
|
|
gg.metrics_horizontal.height = gg.metrics.height;
|
|
gg.metrics_horizontal.horizontal.bearing_x = gg.metrics.Horizontal.bearingX;
|
|
gg.metrics_horizontal.horizontal.bearing_y = gg.metrics.Horizontal.bearingY;
|
|
gg.metrics_horizontal.horizontal.advance = gg.metrics.Horizontal.advance;
|
|
|
|
gg.metrics_horizontal_x.width = gg.metrics.width;
|
|
gg.metrics_horizontal_x.horizontal.bearing_x = gg.metrics.Horizontal.bearingX;
|
|
gg.metrics_horizontal_x.horizontal.advance = gg.metrics.Horizontal.advance;
|
|
|
|
gg.metrics_horizontal_adv.horizontal.advance = gg.metrics.Horizontal.advance;
|
|
gg.origin_x = static_cast<float>(gg.bbox_x0);
|
|
gg.origin_y = static_cast<float>(gg.bbox_y0);
|
|
gg.metrics_initialized = true;
|
|
}
|
|
|
|
void BuildBoundingOutline(GeneratedGlyph& gg) {
|
|
const float left = gg.metrics.Horizontal.bearingX;
|
|
const float top = gg.metrics.Horizontal.bearingY;
|
|
const float right = gg.metrics.Horizontal.bearingX + gg.metrics.width;
|
|
const float bottom = gg.metrics.Horizontal.bearingY - gg.metrics.height;
|
|
|
|
gg.outline_points.clear();
|
|
gg.outline_tags.clear();
|
|
gg.outline_contours.clear();
|
|
|
|
gg.outline_points.push_back({left, top});
|
|
gg.outline_points.push_back({right, top});
|
|
gg.outline_points.push_back({right, bottom});
|
|
gg.outline_points.push_back({left, bottom});
|
|
|
|
gg.outline_tags.resize(gg.outline_points.size(), 1);
|
|
gg.outline_contours.push_back(static_cast<u16>(gg.outline_points.size() - 1));
|
|
|
|
gg.outline.points_ptr = gg.outline_points.data();
|
|
gg.outline.tags_ptr = gg.outline_tags.data();
|
|
gg.outline.contour_end_idx = gg.outline_contours.data();
|
|
gg.outline.points_cnt = static_cast<s16>(gg.outline_points.size());
|
|
gg.outline.contours_cnt = static_cast<s16>(gg.outline_contours.size());
|
|
gg.outline_initialized = true;
|
|
}
|
|
|
|
bool BuildTrueOutline(GeneratedGlyph& gg) {
|
|
auto* st = TryGetState(gg.owner_handle);
|
|
if (!st) {
|
|
return false;
|
|
}
|
|
|
|
FT_Face primary_face = nullptr;
|
|
if (!ResolveFace(*st, gg.owner_handle, primary_face)) {
|
|
return false;
|
|
}
|
|
|
|
const auto* native =
|
|
reinterpret_cast<const Libraries::Font::FontHandleOpaqueNative*>(gg.owner_handle);
|
|
if (!native || native->magic != 0x0F02) {
|
|
return false;
|
|
}
|
|
|
|
u32 resolved_code = gg.codepoint;
|
|
u32 resolved_font_id = 0;
|
|
FT_Face resolved_face = primary_face;
|
|
float resolved_scale_factor = 1.0f;
|
|
float range_scale = 1.0f;
|
|
s32 shift_x_units = 0;
|
|
s32 shift_y_units = 0;
|
|
|
|
if (native->open_info.fontset_record) {
|
|
const auto* fontset_record =
|
|
static_cast<const FontSetRecordHeader*>(native->open_info.fontset_record);
|
|
const bool is_internal_fontset_record =
|
|
fontset_record &&
|
|
fontset_record->magic == Libraries::Font::Internal::FontSetSelector::kMagic;
|
|
u32 mapped_font_id = 0;
|
|
resolved_code = ResolveSysFontCodepoint(native->open_info.fontset_record,
|
|
static_cast<u64>(gg.codepoint),
|
|
native->open_info.fontset_flags, &mapped_font_id);
|
|
if (resolved_code == 0) {
|
|
return false;
|
|
}
|
|
|
|
resolved_font_id = mapped_font_id;
|
|
if (!is_internal_fontset_record) {
|
|
resolved_scale_factor = st->system_font_scale_factor;
|
|
}
|
|
if (st->system_requested) {
|
|
if (mapped_font_id == st->system_font_id) {
|
|
resolved_face = st->ext_ft_face;
|
|
shift_y_units = st->system_font_shift_value;
|
|
} else {
|
|
resolved_face = nullptr;
|
|
for (const auto& fb : st->system_fallback_faces) {
|
|
if (fb.ready && fb.ft_face && fb.font_id == mapped_font_id) {
|
|
resolved_face = fb.ft_face;
|
|
resolved_scale_factor = fb.scale_factor;
|
|
shift_y_units = fb.shift_value;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!resolved_face) {
|
|
return false;
|
|
}
|
|
if (!is_internal_fontset_record) {
|
|
if (shift_y_units == 0) {
|
|
shift_y_units = GetSysFontDesc(mapped_font_id).shift_value;
|
|
}
|
|
if (const std::uint8_t* range_rec =
|
|
FindSysFontRangeRecord(mapped_font_id, resolved_code)) {
|
|
struct SysFontRangeRecordLocal {
|
|
/*0x00*/ u32 start;
|
|
/*0x04*/ u32 end;
|
|
/*0x08*/ s16 shift_x;
|
|
/*0x0A*/ s16 shift_y;
|
|
/*0x0C*/ float scale_mul;
|
|
/*0x10*/ u32 reserved_0x10;
|
|
/*0x14*/ u32 reserved_0x14;
|
|
/*0x18*/ u32 reserved_0x18;
|
|
};
|
|
static_assert(sizeof(SysFontRangeRecordLocal) == 0x1C);
|
|
SysFontRangeRecordLocal rec{};
|
|
std::memcpy(&rec, range_rec, sizeof(rec));
|
|
shift_x_units = static_cast<s32>(rec.shift_x);
|
|
shift_y_units += static_cast<s32>(rec.shift_y);
|
|
range_scale = rec.scale_mul;
|
|
if (range_scale == 0.0f) {
|
|
range_scale = 1.0f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!resolved_face) {
|
|
return false;
|
|
}
|
|
|
|
const FT_UInt glyph_index =
|
|
FT_Get_Char_Index(resolved_face, static_cast<FT_ULong>(resolved_code));
|
|
if (glyph_index == 0) {
|
|
return false;
|
|
}
|
|
|
|
const float scaled_w = gg.glyph.scale_x * resolved_scale_factor * range_scale;
|
|
const float scaled_h = gg.glyph.base_scale * resolved_scale_factor * range_scale;
|
|
const auto char_w = static_cast<FT_F26Dot6>(static_cast<s32>(scaled_w * 64.0f));
|
|
const auto char_h = static_cast<FT_F26Dot6>(static_cast<s32>(scaled_h * 64.0f));
|
|
if (FT_Set_Char_Size(resolved_face, char_w, char_h, 72, 72) != 0) {
|
|
return false;
|
|
}
|
|
|
|
if (shift_x_units != 0 || shift_y_units != 0) {
|
|
if (!resolved_face->size) {
|
|
return false;
|
|
}
|
|
const long x_scale = static_cast<long>(resolved_face->size->metrics.x_scale);
|
|
const long y_scale = static_cast<long>(resolved_face->size->metrics.y_scale);
|
|
|
|
const auto round_fixed_mul = [](long fixed_16_16, s32 value) -> s32 {
|
|
const long long prod =
|
|
static_cast<long long>(fixed_16_16) * static_cast<long long>(value);
|
|
const long long sign_adj =
|
|
(static_cast<long long>(~static_cast<long long>(value)) >> 63) * -0x10000LL;
|
|
const long long base = sign_adj + prod;
|
|
long long tmp = base - 0x8000LL;
|
|
if (tmp < 0) {
|
|
tmp = base + 0x7FFFLL;
|
|
}
|
|
return static_cast<s32>(static_cast<u64>(tmp) >> 16);
|
|
};
|
|
|
|
FT_Vector delta{};
|
|
delta.x = static_cast<FT_Pos>(round_fixed_mul(x_scale, shift_x_units));
|
|
delta.y = static_cast<FT_Pos>(round_fixed_mul(y_scale, shift_y_units));
|
|
FT_Set_Transform(resolved_face, nullptr, &delta);
|
|
} else {
|
|
FT_Set_Transform(resolved_face, nullptr, nullptr);
|
|
}
|
|
|
|
constexpr FT_Int32 kFtLoadFlagsBase =
|
|
static_cast<FT_Int32>(FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP);
|
|
const bool loaded = (FT_Load_Glyph(resolved_face, glyph_index, kFtLoadFlagsBase) == 0);
|
|
FT_Set_Transform(resolved_face, nullptr, nullptr);
|
|
if (!loaded) {
|
|
return false;
|
|
}
|
|
|
|
const FT_GlyphSlot slot = resolved_face->glyph;
|
|
if (!slot || slot->format != FT_GLYPH_FORMAT_OUTLINE) {
|
|
return false;
|
|
}
|
|
|
|
const FT_Outline& outline = slot->outline;
|
|
if (outline.n_points <= 0 || outline.n_contours <= 0 || !outline.points || !outline.tags ||
|
|
!outline.contours) {
|
|
return false;
|
|
}
|
|
|
|
gg.outline_points.clear();
|
|
gg.outline_tags.clear();
|
|
gg.outline_contours.clear();
|
|
gg.outline_points.reserve(static_cast<std::size_t>(outline.n_points));
|
|
gg.outline_tags.reserve(static_cast<std::size_t>(outline.n_points));
|
|
gg.outline_contours.reserve(static_cast<std::size_t>(outline.n_contours));
|
|
|
|
for (int i = 0; i < outline.n_points; ++i) {
|
|
const FT_Vector& p = outline.points[i];
|
|
gg.outline_points.push_back(
|
|
{static_cast<float>(p.x) / 64.0f, static_cast<float>(p.y) / 64.0f});
|
|
const std::uint8_t tag = outline.tags[i];
|
|
gg.outline_tags.push_back(FT_CURVE_TAG(tag) == FT_CURVE_TAG_ON ? 1 : 0);
|
|
}
|
|
for (int c = 0; c < outline.n_contours; ++c) {
|
|
gg.outline_contours.push_back(static_cast<u16>(outline.contours[c]));
|
|
}
|
|
|
|
const bool ok = !gg.outline_points.empty() && !gg.outline_contours.empty();
|
|
if (!ok) {
|
|
return false;
|
|
}
|
|
|
|
gg.outline.points_ptr = gg.outline_points.data();
|
|
gg.outline.tags_ptr = gg.outline_tags.data();
|
|
gg.outline.contour_end_idx = gg.outline_contours.data();
|
|
gg.outline.points_cnt = static_cast<s16>(gg.outline_points.size());
|
|
gg.outline.contours_cnt = static_cast<s16>(gg.outline_contours.size());
|
|
gg.outline_initialized = true;
|
|
return true;
|
|
}
|
|
|
|
u16 ClampToU16(float value) {
|
|
if (value <= 0.0f) {
|
|
return 0;
|
|
}
|
|
const float clamped = std::min(value, static_cast<float>(std::numeric_limits<u16>::max()));
|
|
return static_cast<u16>(std::lround(clamped));
|
|
}
|
|
|
|
FontState& GetState(Libraries::Font::OrbisFontHandle h) {
|
|
return g_font_state[h];
|
|
}
|
|
|
|
FontState* TryGetState(Libraries::Font::OrbisFontHandle h) {
|
|
if (!h)
|
|
return nullptr;
|
|
auto it = g_font_state.find(h);
|
|
if (it == g_font_state.end())
|
|
return nullptr;
|
|
return &it->second;
|
|
}
|
|
|
|
LibraryState& GetLibState(Libraries::Font::OrbisFontLib lib) {
|
|
return g_library_state[lib];
|
|
}
|
|
|
|
void RemoveLibState(Libraries::Font::OrbisFontLib lib) {
|
|
if (auto it = g_library_state.find(lib); it != g_library_state.end()) {
|
|
if (it->second.owned_device_cache) {
|
|
delete[] static_cast<std::uint8_t*>(it->second.owned_device_cache);
|
|
}
|
|
g_library_state.erase(it);
|
|
}
|
|
}
|
|
|
|
void LogExternalFormatSupport(u32 formats_mask) {
|
|
LOG_INFO(Lib_Font, "ExternalFormatsMask=0x{:X}", formats_mask);
|
|
}
|
|
|
|
bool LoadFontFile(const std::filesystem::path& path, std::vector<unsigned char>& out_bytes);
|
|
|
|
void LogFontOpenParams(const Libraries::Font::OrbisFontOpenParams* params) {
|
|
if (!params) {
|
|
LOG_INFO(Lib_Font, "OpenFontSetParams: <null>");
|
|
return;
|
|
}
|
|
LOG_INFO(
|
|
Lib_Font,
|
|
"OpenFontSetParams: tag=0x{:04X} flags=0x{:X} subfont={} unique_id={} reserved_ptrs=[{}, "
|
|
"{}]",
|
|
params->tag, params->flags, params->subfont_index, params->unique_id, params->reserved_ptr1,
|
|
params->reserved_ptr2);
|
|
}
|
|
|
|
std::filesystem::path ResolveGuestPath(const char* guest_path) {
|
|
if (!guest_path) {
|
|
return {};
|
|
}
|
|
if (guest_path[0] != '/') {
|
|
return std::filesystem::path(guest_path);
|
|
}
|
|
if (!g_mnt) {
|
|
g_mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
|
}
|
|
if (!g_mnt) {
|
|
return {};
|
|
}
|
|
return g_mnt->GetHostPath(guest_path);
|
|
}
|
|
|
|
bool LoadGuestFileBytes(const std::filesystem::path& host_path,
|
|
std::vector<unsigned char>& out_bytes) {
|
|
std::ifstream file(host_path, std::ios::binary | std::ios::ate);
|
|
if (!file) {
|
|
return false;
|
|
}
|
|
const std::streamoff size = file.tellg();
|
|
if (size < 0) {
|
|
return false;
|
|
}
|
|
if (size == 0) {
|
|
out_bytes.clear();
|
|
return true;
|
|
}
|
|
if (static_cast<std::uint64_t>(size) > std::numeric_limits<std::size_t>::max()) {
|
|
return false;
|
|
}
|
|
out_bytes.resize(static_cast<std::size_t>(size));
|
|
file.seekg(0, std::ios::beg);
|
|
if (!file.read(reinterpret_cast<char*>(out_bytes.data()),
|
|
static_cast<std::streamsize>(out_bytes.size()))) {
|
|
out_bytes.clear();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
FaceMetrics LoadFaceMetrics(FT_Face face) {
|
|
FaceMetrics m{};
|
|
if (!face) {
|
|
return m;
|
|
}
|
|
m.units_per_em = static_cast<int>(face->units_per_EM);
|
|
|
|
if (const auto* hhea =
|
|
static_cast<const TT_HoriHeader*>(FT_Get_Sfnt_Table(face, ft_sfnt_hhea))) {
|
|
m.hhea_ascent = static_cast<int>(hhea->Ascender);
|
|
m.hhea_descent = static_cast<int>(hhea->Descender);
|
|
m.hhea_lineGap = static_cast<int>(hhea->Line_Gap);
|
|
} else {
|
|
m.hhea_ascent = static_cast<int>(face->ascender);
|
|
m.hhea_descent = static_cast<int>(face->descender);
|
|
const int height = static_cast<int>(face->height);
|
|
m.hhea_lineGap = height - (m.hhea_ascent - m.hhea_descent);
|
|
}
|
|
|
|
if (const auto* os2 = static_cast<const TT_OS2*>(FT_Get_Sfnt_Table(face, ft_sfnt_os2))) {
|
|
m.has_typo = true;
|
|
m.typo_ascent = static_cast<int>(os2->sTypoAscender);
|
|
m.typo_descent = static_cast<int>(os2->sTypoDescender);
|
|
m.typo_lineGap = static_cast<int>(os2->sTypoLineGap);
|
|
m.use_typo = (os2->fsSelection & (1u << 7)) != 0;
|
|
}
|
|
return m;
|
|
}
|
|
|
|
void PopulateStateMetrics(FontState& st, const FaceMetrics& m) {
|
|
st.ext_units_per_em = m.units_per_em;
|
|
st.ext_ascent = m.use_typo && m.has_typo ? m.typo_ascent : m.hhea_ascent;
|
|
st.ext_descent = m.use_typo && m.has_typo ? m.typo_descent : m.hhea_descent;
|
|
st.ext_lineGap = m.use_typo && m.has_typo ? m.typo_lineGap : m.hhea_lineGap;
|
|
st.ext_typo_ascent = m.typo_ascent;
|
|
st.ext_typo_descent = m.typo_descent;
|
|
st.ext_typo_lineGap = m.typo_lineGap;
|
|
st.ext_use_typo_metrics = m.use_typo && m.has_typo;
|
|
}
|
|
|
|
float ComputeScaleExtForState(const FontState& st, FT_Face face, float pixel_h) {
|
|
if (st.ext_units_per_em > 0) {
|
|
return pixel_h / static_cast<float>(st.ext_units_per_em);
|
|
}
|
|
if (!face || face->units_per_EM == 0) {
|
|
return 0.0f;
|
|
}
|
|
return pixel_h / static_cast<float>(face->units_per_EM);
|
|
}
|
|
|
|
float ComputeScaleForPixelHeight(const FontState& st, float pixel_h) {
|
|
if (st.ext_units_per_em <= 0) {
|
|
return 0.0f;
|
|
}
|
|
return pixel_h / static_cast<float>(st.ext_units_per_em);
|
|
}
|
|
|
|
const float kPointsPerInch = 72.0f;
|
|
const float kScaleEpsilon = 1e-4f;
|
|
|
|
float SafeDpiToFloat(u32 dpi) {
|
|
return dpi == 0 ? kPointsPerInch : static_cast<float>(dpi);
|
|
}
|
|
|
|
float PointsToPixels(float pt, u32 dpi) {
|
|
return pt * SafeDpiToFloat(dpi) / kPointsPerInch;
|
|
}
|
|
|
|
float PixelsToPoints(float px, u32 dpi) {
|
|
const float dpi_f = SafeDpiToFloat(dpi);
|
|
return px * kPointsPerInch / dpi_f;
|
|
}
|
|
|
|
const u16 kStyleFrameMagic = 0x0F09;
|
|
const u8 kStyleFrameFlagScale = 0x01;
|
|
const u8 kStyleFrameFlagSlant = 0x02;
|
|
const u8 kStyleFrameFlagWeight = 0x04;
|
|
|
|
StyleFrameScaleState ResolveStyleFrameScale(const OrbisFontStyleFrame* style, const FontState& st) {
|
|
StyleFrameScaleState resolved{
|
|
.scale_w = st.scale_w,
|
|
.scale_h = st.scale_h,
|
|
.dpi_x = st.dpi_x,
|
|
.dpi_y = st.dpi_y,
|
|
.scale_overridden = false,
|
|
.dpi_overridden = false,
|
|
};
|
|
if (!style || style->magic != kStyleFrameMagic) {
|
|
return resolved;
|
|
}
|
|
if ((style->flags1 & kStyleFrameFlagScale) != 0) {
|
|
const bool unit_is_pixel = (style->scaleUnit == 0);
|
|
const u32 dpi_x = style->hDpi;
|
|
const u32 dpi_y = style->vDpi;
|
|
resolved.scale_w = unit_is_pixel ? style->scalePixelW
|
|
: (dpi_x ? PointsToPixels(style->scalePixelW, dpi_x)
|
|
: style->scalePixelW);
|
|
resolved.scale_h = unit_is_pixel ? style->scalePixelH
|
|
: (dpi_y ? PointsToPixels(style->scalePixelH, dpi_y)
|
|
: style->scalePixelH);
|
|
resolved.scale_overridden = true;
|
|
}
|
|
if (!resolved.scale_overridden && st.scale_point_active) {
|
|
resolved.scale_w = PointsToPixels(st.scale_point_w, resolved.dpi_x);
|
|
resolved.scale_h = PointsToPixels(st.scale_point_h, resolved.dpi_y);
|
|
}
|
|
return resolved;
|
|
}
|
|
|
|
void InitializeStyleFrame(OrbisFontStyleFrame& frame) {
|
|
std::memset(&frame, 0, sizeof(frame));
|
|
frame.magic = kStyleFrameMagic;
|
|
frame.hDpi = 72;
|
|
frame.vDpi = 72;
|
|
}
|
|
|
|
bool EnsureStyleFrameInitialized(OrbisFontStyleFrame* frame) {
|
|
return ValidateStyleFramePtr(frame);
|
|
}
|
|
|
|
bool ValidateStyleFramePtr(const OrbisFontStyleFrame* frame) {
|
|
return frame && frame->magic == kStyleFrameMagic;
|
|
}
|
|
|
|
void UpdateCachedLayout(FontState& st) {
|
|
if (!st.ext_face_ready || !st.ext_ft_face) {
|
|
return;
|
|
}
|
|
if (st.ext_scale_for_height == 0.0f) {
|
|
st.ext_scale_for_height =
|
|
ComputeScaleExtForState(st, st.ext_ft_face, st.scale_h * st.system_font_scale_factor);
|
|
}
|
|
const float scale = st.ext_scale_for_height;
|
|
st.cached_baseline_y = static_cast<float>(st.ext_ascent) * scale;
|
|
st.layout_cached = true;
|
|
}
|
|
|
|
std::uint64_t MakeGlyphKey(u32 code, int pixel_h) {
|
|
return (static_cast<std::uint64_t>(code) << 32) | static_cast<std::uint32_t>(pixel_h);
|
|
}
|
|
|
|
void ClearRenderOutputs(Libraries::Font::OrbisFontGlyphMetrics* metrics,
|
|
Libraries::Font::OrbisFontRenderOutput* result) {
|
|
if (metrics) {
|
|
*metrics = {};
|
|
}
|
|
if (result) {
|
|
*result = {};
|
|
}
|
|
}
|
|
|
|
bool ResolveFaceAndScale(FontState& st, Libraries::Font::OrbisFontHandle handle, float pixel_h,
|
|
FT_Face& face_out, float& scale_y_out) {
|
|
face_out = nullptr;
|
|
scale_y_out = 0.0f;
|
|
if (st.ext_face_ready) {
|
|
face_out = st.ext_ft_face;
|
|
}
|
|
if (!face_out) {
|
|
bool system_attached = false;
|
|
const std::string sys_log = ReportSystemFaceRequest(st, handle, system_attached);
|
|
if (!sys_log.empty()) {
|
|
LOG_ERROR(Lib_Font, "{}", sys_log);
|
|
}
|
|
if (system_attached) {
|
|
face_out = st.ext_ft_face;
|
|
}
|
|
}
|
|
if (pixel_h <= kScaleEpsilon) {
|
|
return false;
|
|
}
|
|
if (!face_out) {
|
|
return false;
|
|
}
|
|
scale_y_out = ComputeScaleExtForState(st, face_out, pixel_h * st.system_font_scale_factor);
|
|
return scale_y_out > kScaleEpsilon;
|
|
}
|
|
|
|
bool ResolveFace(FontState& st, Libraries::Font::OrbisFontHandle handle, FT_Face& face_out) {
|
|
face_out = nullptr;
|
|
if (st.ext_face_ready) {
|
|
face_out = st.ext_ft_face;
|
|
}
|
|
if (!face_out) {
|
|
bool system_attached = false;
|
|
const std::string sys_log = ReportSystemFaceRequest(st, handle, system_attached);
|
|
if (!sys_log.empty()) {
|
|
LOG_ERROR(Lib_Font, "{}", sys_log);
|
|
}
|
|
if (system_attached) {
|
|
face_out = st.ext_ft_face;
|
|
}
|
|
}
|
|
return face_out != nullptr;
|
|
}
|
|
|
|
s32 RenderCodepointToSurface(FontState& st, Libraries::Font::OrbisFontHandle handle, FT_Face face,
|
|
float pixel_w, float pixel_h,
|
|
Libraries::Font::OrbisFontRenderSurface* surf, u32 code, float x,
|
|
float y, Libraries::Font::OrbisFontGlyphMetrics* metrics,
|
|
Libraries::Font::OrbisFontRenderOutput* result, s32 shift_x_units,
|
|
s32 shift_y_units) {
|
|
ClearRenderOutputs(metrics, result);
|
|
|
|
if (!surf || !metrics || !result) {
|
|
return ORBIS_FONT_ERROR_INVALID_PARAMETER;
|
|
}
|
|
if (!surf->buffer || surf->width <= 0 || surf->height <= 0 || surf->widthByte <= 0 ||
|
|
surf->pixelSizeByte <= 0) {
|
|
return ORBIS_FONT_ERROR_NO_SUPPORT_SURFACE;
|
|
}
|
|
|
|
const int bpp = static_cast<int>(surf->pixelSizeByte);
|
|
if (bpp != 1 && bpp != 4) {
|
|
return ORBIS_FONT_ERROR_NO_SUPPORT_SURFACE;
|
|
}
|
|
|
|
FT_Face primary_face = face;
|
|
if (!primary_face) {
|
|
float primary_scale_y = 0.0f;
|
|
if (!ResolveFaceAndScale(st, handle, pixel_h, primary_face, primary_scale_y)) {
|
|
return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH;
|
|
}
|
|
}
|
|
|
|
auto resolve_scale_factor = [&](FT_Face candidate) -> float {
|
|
if (!candidate) {
|
|
return st.system_font_scale_factor;
|
|
}
|
|
if (candidate == st.ext_ft_face) {
|
|
return st.system_font_scale_factor;
|
|
}
|
|
for (const auto& fb : st.system_fallback_faces) {
|
|
if (fb.ready && fb.ft_face == candidate) {
|
|
return fb.scale_factor;
|
|
}
|
|
}
|
|
return st.system_font_scale_factor;
|
|
};
|
|
|
|
FT_Face resolved_face = primary_face;
|
|
FT_UInt resolved_glyph_index =
|
|
resolved_face ? FT_Get_Char_Index(resolved_face, static_cast<FT_ULong>(code)) : 0;
|
|
float resolved_scale_factor = resolve_scale_factor(resolved_face);
|
|
if (resolved_glyph_index == 0) {
|
|
if (st.ext_face_ready && st.ext_ft_face && resolved_face != st.ext_ft_face) {
|
|
const FT_UInt gi = FT_Get_Char_Index(st.ext_ft_face, static_cast<FT_ULong>(code));
|
|
if (gi != 0) {
|
|
resolved_face = st.ext_ft_face;
|
|
resolved_glyph_index = gi;
|
|
resolved_scale_factor = st.system_font_scale_factor;
|
|
}
|
|
}
|
|
if (resolved_glyph_index == 0) {
|
|
for (const auto& fb : st.system_fallback_faces) {
|
|
if (!fb.ready || !fb.ft_face) {
|
|
continue;
|
|
}
|
|
const FT_UInt gi = FT_Get_Char_Index(fb.ft_face, static_cast<FT_ULong>(code));
|
|
if (gi == 0) {
|
|
continue;
|
|
}
|
|
resolved_face = fb.ft_face;
|
|
resolved_glyph_index = gi;
|
|
resolved_scale_factor = fb.scale_factor;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!resolved_face || resolved_glyph_index == 0) {
|
|
return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH;
|
|
}
|
|
|
|
const auto set_size = [&](float w, float h) -> bool {
|
|
const auto char_w = static_cast<FT_F26Dot6>(static_cast<s32>(w * 64.0f));
|
|
const auto char_h = static_cast<FT_F26Dot6>(static_cast<s32>(h * 64.0f));
|
|
return FT_Set_Char_Size(resolved_face, char_w, char_h, 72, 72) == 0;
|
|
};
|
|
if (!set_size(pixel_w * resolved_scale_factor, pixel_h * resolved_scale_factor)) {
|
|
return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH;
|
|
}
|
|
|
|
const float frac_x = x - std::floor(x);
|
|
const float frac_y = y - std::floor(y);
|
|
FT_Vector delta{};
|
|
delta.x = static_cast<FT_Pos>(static_cast<s32>(frac_x * 64.0f));
|
|
delta.y = static_cast<FT_Pos>(-static_cast<s32>(frac_y * 64.0f));
|
|
|
|
if ((shift_x_units != 0 || shift_y_units != 0) && resolved_face->size) {
|
|
const long x_scale = static_cast<long>(resolved_face->size->metrics.x_scale);
|
|
const long y_scale = static_cast<long>(resolved_face->size->metrics.y_scale);
|
|
|
|
const auto round_fixed_mul = [](long fixed_16_16, s32 value) -> s32 {
|
|
const long long prod =
|
|
static_cast<long long>(fixed_16_16) * static_cast<long long>(value);
|
|
const long long sign_adj =
|
|
(static_cast<long long>(~static_cast<long long>(value)) >> 63) * -0x10000LL;
|
|
const long long base = sign_adj + prod;
|
|
long long tmp = base - 0x8000LL;
|
|
if (tmp < 0) {
|
|
tmp = base + 0x7FFFLL;
|
|
}
|
|
return static_cast<s32>(static_cast<u64>(tmp) >> 16);
|
|
};
|
|
|
|
delta.x += static_cast<FT_Pos>(round_fixed_mul(x_scale, shift_x_units));
|
|
delta.y += static_cast<FT_Pos>(round_fixed_mul(y_scale, shift_y_units));
|
|
}
|
|
FT_Set_Transform(resolved_face, nullptr, &delta);
|
|
|
|
if (FT_Load_Glyph(resolved_face, resolved_glyph_index, kFtLoadFlagsRender) != 0) {
|
|
FT_Set_Transform(resolved_face, nullptr, nullptr);
|
|
return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH;
|
|
}
|
|
std::vector<unsigned char> glyph_bitmap;
|
|
int x0 = 0, y0 = 0;
|
|
int glyph_w = 0;
|
|
int glyph_h = 0;
|
|
|
|
const FT_GlyphSlot slot = resolved_face->glyph;
|
|
glyph_w = static_cast<int>(slot->bitmap.width);
|
|
glyph_h = static_cast<int>(slot->bitmap.rows);
|
|
x0 = static_cast<int>(slot->bitmap_left);
|
|
y0 = -static_cast<int>(slot->bitmap_top);
|
|
FT_Set_Transform(resolved_face, nullptr, nullptr);
|
|
|
|
const float bearing_x = static_cast<float>(slot->metrics.horiBearingX) / 64.0f;
|
|
const float bearing_y = static_cast<float>(slot->metrics.horiBearingY) / 64.0f;
|
|
const float advance = static_cast<float>(slot->metrics.horiAdvance) / 64.0f;
|
|
const float width_px = static_cast<float>(slot->metrics.width) / 64.0f;
|
|
const float height_px = static_cast<float>(slot->metrics.height) / 64.0f;
|
|
|
|
metrics->width = width_px;
|
|
metrics->height = height_px;
|
|
metrics->Horizontal.bearingX = bearing_x;
|
|
metrics->Horizontal.bearingY = bearing_y;
|
|
metrics->Horizontal.advance = advance;
|
|
metrics->Vertical.bearingX = 0.0f;
|
|
metrics->Vertical.bearingY = 0.0f;
|
|
metrics->Vertical.advance = 0.0f;
|
|
|
|
glyph_bitmap.resize(static_cast<std::size_t>(glyph_w) * static_cast<std::size_t>(glyph_h));
|
|
if (glyph_w > 0 && glyph_h > 0) {
|
|
const int pitch = static_cast<int>(slot->bitmap.pitch);
|
|
const unsigned char* src = reinterpret_cast<const unsigned char*>(slot->bitmap.buffer);
|
|
if (slot->bitmap.pixel_mode == FT_PIXEL_MODE_MONO) {
|
|
for (int row = 0; row < glyph_h; ++row) {
|
|
const unsigned char* src_row = src + static_cast<std::size_t>(row) * pitch;
|
|
unsigned char* dst_row =
|
|
glyph_bitmap.data() + static_cast<std::size_t>(row) * glyph_w;
|
|
for (int col = 0; col < glyph_w; ++col) {
|
|
const unsigned char byte = src_row[col >> 3];
|
|
const unsigned char bit = static_cast<unsigned char>(0x80u >> (col & 7));
|
|
dst_row[col] = (byte & bit) ? 0xFFu : 0x00u;
|
|
}
|
|
}
|
|
} else {
|
|
for (int row = 0; row < glyph_h; ++row) {
|
|
const unsigned char* src_row = src + static_cast<std::size_t>(row) * pitch;
|
|
unsigned char* dst_row =
|
|
glyph_bitmap.data() + static_cast<std::size_t>(row) * glyph_w;
|
|
std::memcpy(dst_row, src_row, static_cast<std::size_t>(glyph_w));
|
|
}
|
|
}
|
|
}
|
|
|
|
const int dest_x = static_cast<int>(std::floor(x)) + x0;
|
|
const int dest_y = static_cast<int>(std::floor(y)) + y0;
|
|
|
|
const int surface_w = std::max(surf->width, 0);
|
|
const int surface_h = std::max(surf->height, 0);
|
|
const int clip_x0 = std::clamp(static_cast<int>(surf->sc_x0), 0, surface_w);
|
|
const int clip_y0 = std::clamp(static_cast<int>(surf->sc_y0), 0, surface_h);
|
|
const int clip_x1 = std::clamp(static_cast<int>(surf->sc_x1), 0, surface_w);
|
|
const int clip_y1 = std::clamp(static_cast<int>(surf->sc_y1), 0, surface_h);
|
|
|
|
int update_x0 = dest_x;
|
|
int update_y0 = dest_y;
|
|
int update_w = 0;
|
|
int update_h = 0;
|
|
|
|
if (glyph_w > 0 && glyph_h > 0 && clip_x1 > clip_x0 && clip_y1 > clip_y0) {
|
|
auto* dst_base = static_cast<std::uint8_t*>(surf->buffer);
|
|
const int start_row = std::max(dest_y, clip_y0);
|
|
const int end_row = std::min(dest_y + glyph_h, clip_y1);
|
|
const int start_col = std::max(dest_x, clip_x0);
|
|
const int end_col = std::min(dest_x + glyph_w, clip_x1);
|
|
|
|
update_x0 = start_col;
|
|
update_y0 = start_row;
|
|
update_w = std::max(0, end_col - start_col);
|
|
update_h = std::max(0, end_row - start_row);
|
|
|
|
for (int row = start_row; row < end_row; ++row) {
|
|
const int src_y = row - dest_y;
|
|
if (src_y < 0 || src_y >= glyph_h)
|
|
continue;
|
|
const std::uint8_t* src_row =
|
|
glyph_bitmap.data() + static_cast<std::size_t>(src_y) * glyph_w;
|
|
std::uint8_t* dst_row = dst_base + static_cast<std::size_t>(row) *
|
|
static_cast<std::size_t>(surf->widthByte);
|
|
for (int col = start_col; col < end_col; ++col) {
|
|
const int src_x = col - dest_x;
|
|
if (src_x < 0 || src_x >= glyph_w)
|
|
continue;
|
|
const std::uint8_t cov = src_row[src_x];
|
|
std::uint8_t* dst = dst_row + static_cast<std::size_t>(col) * bpp;
|
|
if (bpp == 1) {
|
|
dst[0] = cov;
|
|
} else {
|
|
dst[0] = cov;
|
|
dst[1] = cov;
|
|
dst[2] = cov;
|
|
dst[3] = cov;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
result->stage = nullptr;
|
|
result->SurfaceImage.address = static_cast<u8*>(surf->buffer);
|
|
result->SurfaceImage.widthByte = static_cast<u32>(surf->widthByte);
|
|
result->SurfaceImage.pixelSizeByte = static_cast<u8>(surf->pixelSizeByte);
|
|
result->SurfaceImage.pixelFormat = 0;
|
|
result->SurfaceImage.pad16 = 0;
|
|
result->UpdateRect.x = static_cast<u32>(std::max(update_x0, 0));
|
|
result->UpdateRect.y = static_cast<u32>(std::max(update_y0, 0));
|
|
result->UpdateRect.w = static_cast<u32>(std::max(update_w, 0));
|
|
result->UpdateRect.h = static_cast<u32>(std::max(update_h, 0));
|
|
result->SurfaceImage.address =
|
|
static_cast<u8*>(surf->buffer) +
|
|
static_cast<std::size_t>(result->UpdateRect.y) * static_cast<std::size_t>(surf->widthByte) +
|
|
static_cast<std::size_t>(result->UpdateRect.x) * static_cast<std::size_t>(bpp);
|
|
const auto floor_int = [](float v) -> int {
|
|
int i = static_cast<int>(std::trunc(v));
|
|
if (static_cast<float>(i) > v) {
|
|
--i;
|
|
}
|
|
return i;
|
|
};
|
|
const auto ceil_int = [](float v) -> int {
|
|
int i = static_cast<int>(std::trunc(v));
|
|
if (static_cast<float>(i) < v) {
|
|
++i;
|
|
}
|
|
return i;
|
|
};
|
|
|
|
const float left_f = x + metrics->Horizontal.bearingX;
|
|
const float top_f = y + metrics->Horizontal.bearingY;
|
|
const float right_f = left_f + metrics->width;
|
|
const float bottom_f = top_f - metrics->height;
|
|
|
|
const int left_i = floor_int(left_f);
|
|
const int top_i = floor_int(top_f);
|
|
const int right_i = ceil_int(right_f);
|
|
const int bottom_i = ceil_int(bottom_f);
|
|
|
|
const float adv_f = x + metrics->Horizontal.advance;
|
|
const float adv_snapped = static_cast<float>(floor_int(adv_f)) - x;
|
|
|
|
result->ImageMetrics.bearingX = static_cast<float>(left_i) - x;
|
|
result->ImageMetrics.bearingY = static_cast<float>(top_i) - y;
|
|
result->ImageMetrics.advance = adv_snapped;
|
|
int stride_i = right_i + 1;
|
|
const float adjust = static_cast<float>(right_i) - right_f;
|
|
const int tmp_i = floor_int(adv_f + adjust);
|
|
const int adv_trunc_i = static_cast<int>(std::trunc(adv_f));
|
|
if (adv_trunc_i == 0) {
|
|
stride_i = tmp_i;
|
|
}
|
|
if (stride_i < tmp_i) {
|
|
stride_i = tmp_i;
|
|
}
|
|
result->ImageMetrics.stride = static_cast<float>(stride_i) - x;
|
|
result->ImageMetrics.width = static_cast<u32>(std::max(0, right_i - left_i));
|
|
result->ImageMetrics.height = static_cast<u32>(std::max(0, top_i - bottom_i));
|
|
return ORBIS_OK;
|
|
}
|
|
|
|
s32 RenderCodepointToSurfaceWithScale(FontState& st, Libraries::Font::OrbisFontHandle handle,
|
|
FT_Face face, float scale_x, float scale_y,
|
|
Libraries::Font::OrbisFontRenderSurface* surf, u32 code,
|
|
float x, float y, Libraries::Font::OrbisFontGlyphMetrics* m,
|
|
Libraries::Font::OrbisFontRenderOutput* result) {
|
|
ClearRenderOutputs(m, result);
|
|
|
|
if (!surf || !m || !result) {
|
|
return ORBIS_FONT_ERROR_INVALID_PARAMETER;
|
|
}
|
|
if (scale_x <= kScaleEpsilon || scale_y <= kScaleEpsilon) {
|
|
return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH;
|
|
}
|
|
if (!surf->buffer || surf->width <= 0 || surf->height <= 0 || surf->widthByte <= 0 ||
|
|
surf->pixelSizeByte <= 0) {
|
|
return ORBIS_FONT_ERROR_NO_SUPPORT_SURFACE;
|
|
}
|
|
const int bpp = static_cast<int>(surf->pixelSizeByte);
|
|
if (bpp != 1 && bpp != 4) {
|
|
return ORBIS_FONT_ERROR_NO_SUPPORT_SURFACE;
|
|
}
|
|
|
|
FT_Face primary_face = face;
|
|
if (!ResolveFace(st, handle, primary_face)) {
|
|
return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH;
|
|
}
|
|
|
|
FT_Face resolved_face = primary_face;
|
|
FT_UInt resolved_glyph_index =
|
|
resolved_face ? FT_Get_Char_Index(resolved_face, static_cast<FT_ULong>(code)) : 0;
|
|
float resolved_scale_factor = st.system_font_scale_factor;
|
|
if (resolved_glyph_index == 0) {
|
|
for (const auto& fb : st.system_fallback_faces) {
|
|
if (!fb.ready || !fb.ft_face) {
|
|
continue;
|
|
}
|
|
const FT_UInt gi = FT_Get_Char_Index(fb.ft_face, static_cast<FT_ULong>(code));
|
|
if (gi == 0) {
|
|
continue;
|
|
}
|
|
resolved_face = fb.ft_face;
|
|
resolved_glyph_index = gi;
|
|
resolved_scale_factor = fb.scale_factor;
|
|
break;
|
|
}
|
|
}
|
|
if (!resolved_face || resolved_glyph_index == 0) {
|
|
return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH;
|
|
}
|
|
|
|
const int base_units = (st.ext_units_per_em > 0)
|
|
? st.ext_units_per_em
|
|
: static_cast<int>(resolved_face->units_per_EM);
|
|
if (base_units <= 0) {
|
|
return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH;
|
|
}
|
|
|
|
const float pixel_w = scale_x * static_cast<float>(base_units) * resolved_scale_factor;
|
|
const float pixel_h = scale_y * static_cast<float>(base_units) * resolved_scale_factor;
|
|
|
|
const auto set_size = [&](float w, float h) -> bool {
|
|
const auto char_w = static_cast<FT_F26Dot6>(static_cast<s32>(w * 64.0f));
|
|
const auto char_h = static_cast<FT_F26Dot6>(static_cast<s32>(h * 64.0f));
|
|
return FT_Set_Char_Size(resolved_face, char_w, char_h, 72, 72) == 0;
|
|
};
|
|
if (!set_size(pixel_w, pixel_h)) {
|
|
return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH;
|
|
}
|
|
|
|
const float frac_x = x - std::floor(x);
|
|
const float frac_y = y - std::floor(y);
|
|
FT_Vector delta{};
|
|
delta.x = static_cast<FT_Pos>(static_cast<s32>(frac_x * 64.0f));
|
|
delta.y = static_cast<FT_Pos>(-static_cast<s32>(frac_y * 64.0f));
|
|
FT_Set_Transform(resolved_face, nullptr, &delta);
|
|
|
|
if (FT_Load_Glyph(resolved_face, resolved_glyph_index, kFtLoadFlagsRender) != 0) {
|
|
FT_Set_Transform(resolved_face, nullptr, nullptr);
|
|
return ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH;
|
|
}
|
|
std::vector<unsigned char> glyph_bitmap;
|
|
int x0 = 0, y0 = 0;
|
|
int glyph_w = 0;
|
|
int glyph_h = 0;
|
|
|
|
const FT_GlyphSlot slot = resolved_face->glyph;
|
|
glyph_w = static_cast<int>(slot->bitmap.width);
|
|
glyph_h = static_cast<int>(slot->bitmap.rows);
|
|
x0 = static_cast<int>(slot->bitmap_left);
|
|
y0 = -static_cast<int>(slot->bitmap_top);
|
|
FT_Set_Transform(resolved_face, nullptr, nullptr);
|
|
|
|
const float bearing_x = static_cast<float>(slot->metrics.horiBearingX) / 64.0f;
|
|
const float bearing_y = static_cast<float>(slot->metrics.horiBearingY) / 64.0f;
|
|
const float advance = static_cast<float>(slot->metrics.horiAdvance) / 64.0f;
|
|
const float width_px = static_cast<float>(slot->metrics.width) / 64.0f;
|
|
const float height_px = static_cast<float>(slot->metrics.height) / 64.0f;
|
|
|
|
m->width = width_px;
|
|
m->height = height_px;
|
|
m->Horizontal.bearingX = bearing_x;
|
|
m->Horizontal.bearingY = bearing_y;
|
|
m->Horizontal.advance = advance;
|
|
m->Vertical.bearingX = 0.0f;
|
|
m->Vertical.bearingY = 0.0f;
|
|
m->Vertical.advance = 0.0f;
|
|
|
|
glyph_bitmap.resize(static_cast<std::size_t>(glyph_w) * static_cast<std::size_t>(glyph_h));
|
|
if (glyph_w > 0 && glyph_h > 0) {
|
|
const int pitch = static_cast<int>(slot->bitmap.pitch);
|
|
const unsigned char* src = reinterpret_cast<const unsigned char*>(slot->bitmap.buffer);
|
|
if (slot->bitmap.pixel_mode == FT_PIXEL_MODE_MONO) {
|
|
for (int row = 0; row < glyph_h; ++row) {
|
|
const unsigned char* src_row = src + static_cast<std::size_t>(row) * pitch;
|
|
unsigned char* dst_row =
|
|
glyph_bitmap.data() + static_cast<std::size_t>(row) * glyph_w;
|
|
for (int col = 0; col < glyph_w; ++col) {
|
|
const unsigned char byte = src_row[col >> 3];
|
|
const unsigned char bit = static_cast<unsigned char>(0x80u >> (col & 7));
|
|
dst_row[col] = (byte & bit) ? 0xFFu : 0x00u;
|
|
}
|
|
}
|
|
} else {
|
|
for (int row = 0; row < glyph_h; ++row) {
|
|
const unsigned char* src_row = src + static_cast<std::size_t>(row) * pitch;
|
|
unsigned char* dst_row =
|
|
glyph_bitmap.data() + static_cast<std::size_t>(row) * glyph_w;
|
|
std::memcpy(dst_row, src_row, static_cast<std::size_t>(glyph_w));
|
|
}
|
|
}
|
|
}
|
|
|
|
const int dest_x = static_cast<int>(std::floor(x)) + x0;
|
|
const int dest_y = static_cast<int>(std::floor(y)) + y0;
|
|
|
|
const int surface_w = std::max(surf->width, 0);
|
|
const int surface_h = std::max(surf->height, 0);
|
|
const int clip_x0 = std::clamp(static_cast<int>(surf->sc_x0), 0, surface_w);
|
|
const int clip_y0 = std::clamp(static_cast<int>(surf->sc_y0), 0, surface_h);
|
|
const int clip_x1 = std::clamp(static_cast<int>(surf->sc_x1), 0, surface_w);
|
|
const int clip_y1 = std::clamp(static_cast<int>(surf->sc_y1), 0, surface_h);
|
|
|
|
int update_x0 = dest_x;
|
|
int update_y0 = dest_y;
|
|
int update_w = 0;
|
|
int update_h = 0;
|
|
|
|
if (glyph_w > 0 && glyph_h > 0 && clip_x1 > clip_x0 && clip_y1 > clip_y0) {
|
|
auto* dst_base = static_cast<std::uint8_t*>(surf->buffer);
|
|
const int start_row = std::max(dest_y, clip_y0);
|
|
const int end_row = std::min(dest_y + glyph_h, clip_y1);
|
|
const int start_col = std::max(dest_x, clip_x0);
|
|
const int end_col = std::min(dest_x + glyph_w, clip_x1);
|
|
|
|
update_x0 = start_col;
|
|
update_y0 = start_row;
|
|
update_w = std::max(0, end_col - start_col);
|
|
update_h = std::max(0, end_row - start_row);
|
|
|
|
for (int row = start_row; row < end_row; ++row) {
|
|
const int src_y = row - dest_y;
|
|
if (src_y < 0 || src_y >= glyph_h)
|
|
continue;
|
|
const std::uint8_t* src_row =
|
|
glyph_bitmap.data() + static_cast<std::size_t>(src_y) * glyph_w;
|
|
std::uint8_t* dst_row = dst_base + static_cast<std::size_t>(row) *
|
|
static_cast<std::size_t>(surf->widthByte);
|
|
for (int col = start_col; col < end_col; ++col) {
|
|
const int src_x = col - dest_x;
|
|
if (src_x < 0 || src_x >= glyph_w)
|
|
continue;
|
|
const std::uint8_t cov = src_row[src_x];
|
|
std::uint8_t* dst = dst_row + static_cast<std::size_t>(col) * bpp;
|
|
if (bpp == 1) {
|
|
dst[0] = cov;
|
|
} else {
|
|
dst[0] = cov;
|
|
dst[1] = cov;
|
|
dst[2] = cov;
|
|
dst[3] = cov;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
result->stage = nullptr;
|
|
result->SurfaceImage.address = static_cast<u8*>(surf->buffer);
|
|
result->SurfaceImage.widthByte = static_cast<u32>(surf->widthByte);
|
|
result->SurfaceImage.pixelSizeByte = static_cast<u8>(surf->pixelSizeByte);
|
|
result->SurfaceImage.pixelFormat = 0;
|
|
result->SurfaceImage.pad16 = 0;
|
|
result->UpdateRect.x = static_cast<u32>(std::max(update_x0, 0));
|
|
result->UpdateRect.y = static_cast<u32>(std::max(update_y0, 0));
|
|
result->UpdateRect.w = static_cast<u32>(std::max(update_w, 0));
|
|
result->UpdateRect.h = static_cast<u32>(std::max(update_h, 0));
|
|
result->SurfaceImage.address =
|
|
static_cast<u8*>(surf->buffer) +
|
|
static_cast<std::size_t>(result->UpdateRect.y) * static_cast<std::size_t>(surf->widthByte) +
|
|
static_cast<std::size_t>(result->UpdateRect.x) * static_cast<std::size_t>(bpp);
|
|
const auto floor_int = [](float v) -> int {
|
|
int i = static_cast<int>(std::trunc(v));
|
|
if (static_cast<float>(i) > v) {
|
|
--i;
|
|
}
|
|
return i;
|
|
};
|
|
const auto ceil_int = [](float v) -> int {
|
|
int i = static_cast<int>(std::trunc(v));
|
|
if (static_cast<float>(i) < v) {
|
|
++i;
|
|
}
|
|
return i;
|
|
};
|
|
|
|
const float left_f = x + m->Horizontal.bearingX;
|
|
const float top_f = y + m->Horizontal.bearingY;
|
|
const float right_f = left_f + m->width;
|
|
const float bottom_f = top_f - m->height;
|
|
|
|
const int left_i = floor_int(left_f);
|
|
const int top_i = floor_int(top_f);
|
|
const int right_i = ceil_int(right_f);
|
|
const int bottom_i = ceil_int(bottom_f);
|
|
|
|
const float adv_f = x + m->Horizontal.advance;
|
|
const float adv_snapped = static_cast<float>(floor_int(adv_f)) - x;
|
|
|
|
result->ImageMetrics.bearingX = static_cast<float>(left_i) - x;
|
|
result->ImageMetrics.bearingY = static_cast<float>(top_i) - y;
|
|
result->ImageMetrics.advance = adv_snapped;
|
|
int stride_i = right_i + 1;
|
|
const float adjust = static_cast<float>(right_i) - right_f;
|
|
const int tmp_i = floor_int(adv_f + adjust);
|
|
const int adv_trunc_i = static_cast<int>(std::trunc(adv_f));
|
|
if (adv_trunc_i == 0) {
|
|
stride_i = tmp_i;
|
|
}
|
|
if (stride_i < tmp_i) {
|
|
stride_i = tmp_i;
|
|
}
|
|
result->ImageMetrics.stride = static_cast<float>(stride_i) - x;
|
|
result->ImageMetrics.width = static_cast<u32>(std::max(0, right_i - left_i));
|
|
result->ImageMetrics.height = static_cast<u32>(std::max(0, top_i - bottom_i));
|
|
return ORBIS_OK;
|
|
}
|
|
|
|
const GlyphEntry* GetGlyphEntry(FontState& st, Libraries::Font::OrbisFontHandle handle, u32 code,
|
|
FT_Face& face_out, float& scale_out) {
|
|
face_out = nullptr;
|
|
scale_out = 0.0f;
|
|
|
|
if (st.ext_face_ready) {
|
|
face_out = st.ext_ft_face;
|
|
}
|
|
if (!face_out) {
|
|
bool system_attached = false;
|
|
const std::string sys_log = ReportSystemFaceRequest(st, handle, system_attached);
|
|
if (!sys_log.empty()) {
|
|
LOG_ERROR(Lib_Font, "SYSTEM_FONT_FAILED");
|
|
LOG_DEBUG(Lib_Font, "{}", sys_log);
|
|
}
|
|
if (system_attached) {
|
|
face_out = st.ext_ft_face;
|
|
}
|
|
}
|
|
if (!face_out) {
|
|
return nullptr;
|
|
}
|
|
|
|
float resolved_scale_factor = st.system_font_scale_factor;
|
|
FT_Face resolved_face = face_out;
|
|
FT_UInt resolved_glyph_index = FT_Get_Char_Index(resolved_face, static_cast<FT_ULong>(code));
|
|
if (resolved_glyph_index == 0) {
|
|
for (const auto& fb : st.system_fallback_faces) {
|
|
if (!fb.ready || !fb.ft_face) {
|
|
continue;
|
|
}
|
|
const FT_UInt gi = FT_Get_Char_Index(fb.ft_face, static_cast<FT_ULong>(code));
|
|
if (gi == 0) {
|
|
continue;
|
|
}
|
|
resolved_face = fb.ft_face;
|
|
resolved_glyph_index = gi;
|
|
resolved_scale_factor = fb.scale_factor;
|
|
break;
|
|
}
|
|
}
|
|
if (!resolved_face || resolved_glyph_index == 0) {
|
|
return nullptr;
|
|
}
|
|
|
|
const float pixel_h_f = st.scale_h * resolved_scale_factor;
|
|
const int pixel_h = std::max(1, static_cast<int>(std::lround(pixel_h_f)));
|
|
const std::uint64_t key = MakeGlyphKey(code, pixel_h);
|
|
|
|
GlyphEntry* ge = nullptr;
|
|
if (auto it = st.ext_cache.find(key); it != st.ext_cache.end()) {
|
|
ge = &it->second;
|
|
}
|
|
if (!ge) {
|
|
const auto set_size = [&](float h) -> bool {
|
|
const auto char_h = static_cast<FT_F26Dot6>(static_cast<s32>(h * 64.0f));
|
|
return FT_Set_Char_Size(resolved_face, 0, char_h, 72, 72) == 0;
|
|
};
|
|
if (!set_size(pixel_h_f)) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (FT_Load_Glyph(resolved_face, resolved_glyph_index, kFtLoadFlagsRender) != 0) {
|
|
return nullptr;
|
|
}
|
|
|
|
const FT_GlyphSlot slot = resolved_face->glyph;
|
|
GlyphEntry entry{};
|
|
entry.w = static_cast<int>(slot->bitmap.width);
|
|
entry.h = static_cast<int>(slot->bitmap.rows);
|
|
entry.x0 = static_cast<int>(slot->bitmap_left);
|
|
entry.y0 = -static_cast<int>(slot->bitmap_top);
|
|
entry.x1 = entry.x0 + entry.w;
|
|
entry.y1 = entry.y0 + entry.h;
|
|
entry.advance = static_cast<float>(slot->metrics.horiAdvance) / 64.0f;
|
|
entry.bearingX = static_cast<float>(slot->bitmap_left);
|
|
ge = &st.ext_cache.emplace(key, std::move(entry)).first->second;
|
|
}
|
|
|
|
face_out = resolved_face;
|
|
scale_out = ComputeScaleExtForState(st, resolved_face, pixel_h_f);
|
|
return ge;
|
|
}
|
|
|
|
constexpr SystemFontDefinition kSystemFontDefinitions[] = {
|
|
{0x18070043, "FONTSET_SST_STD_EUROPEAN_LIGHT", "SST-Light.otf"},
|
|
{0x18070044, "FONTSET_SST_STD_EUROPEAN", "SST-Roman.otf"},
|
|
{0x18070045, "FONTSET_SST_STD_EUROPEAN_MEDIUM", "SST-Medium.otf"},
|
|
{0x18070047, "FONTSET_SST_STD_EUROPEAN_BOLD", "SST-Bold.otf"},
|
|
{0x18070053, "FONTSET_SST_STD_VIETNAMESE_LIGHT", "SSTVietnamese-Light.otf"},
|
|
{0x18070054, "FONTSET_SST_STD_VIETNAMESE", "SSTVietnamese-Roman.otf"},
|
|
{0x18070055, "FONTSET_SST_STD_VIETNAMESE_MEDIUM", "SSTVietnamese-Medium.otf"},
|
|
{0x18070057, "FONTSET_SST_STD_VIETNAMESE_BOLD", "SSTVietnamese-Bold.otf"},
|
|
{0x180700C3, "FONTSET_SST_STD_EUROPEAN_AR_LIGHT", "SST-Light.otf"},
|
|
{0x180700C4, "FONTSET_SST_STD_EUROPEAN_AR", "SST-Roman.otf"},
|
|
{0x180700C5, "FONTSET_SST_STD_EUROPEAN_AR_MEDIUM", "SST-Medium.otf"},
|
|
{0x180700C7, "FONTSET_SST_STD_EUROPEAN_AR_BOLD", "SST-Bold.otf"},
|
|
{0x18070444, "FONTSET_SST_STD_EUROPEAN_JP", "SSTJpPro-Regular.otf"},
|
|
{0x18070447, "FONTSET_SST_STD_EUROPEAN_JP_BOLD", "SSTJpPro-Bold.otf"},
|
|
{0x18070454, "FONTSET_SST_STD_EUROPEAN_JP", "SSTJpPro-Regular.otf"},
|
|
{0x18070457, "FONTSET_SST_STD_EUROPEAN_JP_BOLD", "SSTJpPro-Bold.otf"},
|
|
{0x180704C4, "FONTSET_SST_STD_EUROPEAN_JP_AR", "SSTJpPro-Regular.otf"},
|
|
{0x180704C7, "FONTSET_SST_STD_EUROPEAN_JP_AR_BOLD", "SSTJpPro-Bold.otf"},
|
|
{0x18071053, "FONTSET_SST_STD_THAI_LIGHT", "SSTVietnamese-Light.otf"},
|
|
{0x18071054, "FONTSET_SST_STD_THAI", "SSTVietnamese-Roman.otf"},
|
|
{0x18071055, "FONTSET_SST_STD_THAI_MEDIUM", "SSTVietnamese-Medium.otf"},
|
|
{0x18071057, "FONTSET_SST_STD_THAI_BOLD", "SSTVietnamese-Bold.otf"},
|
|
{0x18071454, "FONTSET_SST_STD_EUROPEAN_JP_TH", "SSTJpPro-Regular.otf"},
|
|
{0x18071457, "FONTSET_SST_STD_EUROPEAN_JP_TH_BOLD", "SSTJpPro-Bold.otf"},
|
|
{0x18072444, "FONTSET_SST_STD_EUROPEAN_JPUH", "SSTJpPro-Regular.otf"},
|
|
{0x18072447, "FONTSET_SST_STD_EUROPEAN_JPUH_BOLD", "SSTJpPro-Bold.otf"},
|
|
{0x180724C4, "FONTSET_SST_STD_EUROPEAN_JPUH_AR", "SSTJpPro-Regular.otf"},
|
|
{0x180724C7, "FONTSET_SST_STD_EUROPEAN_JPUH_AR_BOLD", "SSTJpPro-Bold.otf"},
|
|
{0x18073454, "FONTSET_SST_STD_EUROPEAN_JPUH_TH", "SSTJpPro-Regular.otf"},
|
|
{0x18073457, "FONTSET_SST_STD_EUROPEAN_JPUH_TH_BOLD", "SSTJpPro-Bold.otf"},
|
|
{0x180734D4, "FONTSET_SST_STD_EUROPEAN_JPUH_TH_AR", "SSTJpPro-Regular.otf"},
|
|
{0x180734D7, "FONTSET_SST_STD_EUROPEAN_JPUH_TH_AR_BOLD", "SSTJpPro-Bold.otf"},
|
|
{0x18078044, "FONTSET_SST_STD_EUROPEAN_GB", "DFHEI5-SONY.ttf"},
|
|
{0x180780C4, "FONTSET_SST_STD_EUROPEAN_GB_AR", "DFHEI5-SONY.ttf"},
|
|
{0x18079054, "FONTSET_SST_STD_EUROPEAN_GB_TH", "DFHEI5-SONY.ttf"},
|
|
{0x1807A044, "FONTSET_SST_STD_EUROPEAN_GBUH", "DFHEI5-SONY.ttf"},
|
|
{0x1807A0C4, "FONTSET_SST_STD_EUROPEAN_GBUH_AR", "DFHEI5-SONY.ttf"},
|
|
{0x1807A444, "FONTSET_SST_STD_EUROPEAN_JPCJK", "SSTJpPro-Regular.otf"},
|
|
{0x1807A4C4, "FONTSET_SST_STD_EUROPEAN_JPCJK_AR", "SSTJpPro-Regular.otf"},
|
|
{0x1807AC44, "FONTSET_SST_STD_EUROPEAN_GBCJK", "SCEPS4Yoongd-Medium.otf"},
|
|
{0x1807ACC4, "FONTSET_SST_STD_EUROPEAN_GBCJK_AR", "DFHEI5-SONY.ttf"},
|
|
{0x1807B054, "FONTSET_SST_STD_EUROPEAN_GBUH_TH", "DFHEI5-SONY.ttf"},
|
|
{0x1807B0D4, "FONTSET_SST_STD_EUROPEAN_GBUH_TH_AR", "DFHEI5-SONY.ttf"},
|
|
{0x1807B454, "FONTSET_SST_STD_EUROPEAN_JPCJK_TH", "SSTJpPro-Regular.otf"},
|
|
{0x1807B4D4, "FONTSET_SST_STD_EUROPEAN_JPCJK_TH_AR", "SSTJpPro-Regular.otf"},
|
|
{0x1807BC54, "FONTSET_SST_STD_EUROPEAN_GBCJK_TH", "DFHEI5-SONY.ttf"},
|
|
{0x1807BCD4, "FONTSET_SST_STD_EUROPEAN_GBCJK_TH_AR", "DFHEI5-SONY.ttf"},
|
|
{0x18080444, "FONTSET_SST_STD_JAPANESE_JP", "SSTJpPro-Regular.otf"},
|
|
{0x18080447, "FONTSET_SST_STD_JAPANESE_JP_BOLD", "SSTJpPro-Bold.otf"},
|
|
{0x18080454, "FONTSET_SST_STD_VIETNAMESE_JP", "SSTJpPro-Regular.otf"},
|
|
{0x18080457, "FONTSET_SST_STD_VIETNAMESE_JP_BOLD", "SSTJpPro-Bold.otf"},
|
|
{0x180804C4, "FONTSET_SST_STD_JAPANESE_JP_AR", "SSTJpPro-Regular.otf"},
|
|
{0x180804C7, "FONTSET_SST_STD_JAPANESE_JP_AR_BOLD", "SSTJpPro-Bold.otf"},
|
|
{0x18081454, "FONTSET_SST_STD_ASIAN_JP_TH", "SSTJpPro-Regular.otf"},
|
|
{0x18081457, "FONTSET_SST_STD_ASIAN_JP_TH_BOLD", "SSTJpPro-Bold.otf"},
|
|
{0x18082444, "FONTSET_SST_STD_JAPANESE_JPUH", "SSTJpPro-Regular.otf"},
|
|
{0x18082447, "FONTSET_SST_STD_JAPANESE_JPUH_BOLD", "SSTJpPro-Bold.otf"},
|
|
{0x180824C4, "FONTSET_SST_STD_JAPANESE_JPUH_AR", "SSTJpPro-Regular.otf"},
|
|
{0x180824C7, "FONTSET_SST_STD_JAPANESE_JPUH_AR_BOLD", "SSTJpPro-Bold.otf"},
|
|
{0x18083454, "FONTSET_SST_STD_ASIAN_JPUH_TH", "SSTJpPro-Regular.otf"},
|
|
{0x18083457, "FONTSET_SST_STD_ASIAN_JPUH_TH_BOLD", "SSTJpPro-Bold.otf"},
|
|
{0x180834D4, "FONTSET_SST_STD_ASIAN_JPUH_TH_AR", "SSTJpPro-Regular.otf"},
|
|
{0x180834D7, "FONTSET_SST_STD_ASIAN_JPUH_TH_AR_BOLD", "SSTJpPro-Bold.otf"},
|
|
{0x1808A444, "FONTSET_SST_STD_JAPANESE_JPCJK", "SSTJpPro-Regular.otf"},
|
|
{0x1808A4C4, "FONTSET_SST_STD_JAPANESE_JPCJK_AR", "SSTJpPro-Regular.otf"},
|
|
{0x1808B454, "FONTSET_SST_STD_ASIAN_JPCJK_TH", "SSTJpPro-Regular.otf"},
|
|
{0x1808B4D4, "FONTSET_SST_STD_ASIAN_JPCJK_TH_AR", "SSTJpPro-Regular.otf"},
|
|
{0x180C8044, "FONTSET_SST_STD_SCHINESE_GB", "DFHEI5-SONY.ttf"},
|
|
{0x180C80C4, "FONTSET_SST_STD_SCHINESE_GB_AR", "DFHEI5-SONY.ttf"},
|
|
{0x180C9054, "FONTSET_SST_STD_ASIAN_GB_TH", "DFHEI5-SONY.ttf"},
|
|
{0x180CA044, "FONTSET_SST_STD_SCHINESE_GBUH", "DFHEI5-SONY.ttf"},
|
|
{0x180CA0C4, "FONTSET_SST_STD_SCHINESE_GBUH_AR", "DFHEI5-SONY.ttf"},
|
|
{0x180CAC44, "FONTSET_SST_STD_SCHINESE_GBCJK", "DFHEI5-SONY.ttf"},
|
|
{0x180CACC4, "FONTSET_SST_STD_SCHINESE_GBCJK_AR", "DFHEI5-SONY.ttf"},
|
|
{0x180CB054, "FONTSET_SST_STD_ASIAN_GBUH_TH", "DFHEI5-SONY.ttf"},
|
|
{0x180CB0D4, "FONTSET_SST_STD_ASIAN_GBUH_TH_AR", "DFHEI5-SONY.ttf"},
|
|
{0x180CBC54, "FONTSET_SST_STD_ASIAN_GBCJK_TH", "DFHEI5-SONY.ttf"},
|
|
{0x180CBCD4, "FONTSET_SST_STD_ASIAN_GBCJK_TH_AR", "DFHEI5-SONY.ttf"},
|
|
{0x18170043, "FONTSET_SST_STD_EUROPEAN_LIGHT_ITALIC", "SST-LightItalic.otf"},
|
|
{0x18170044, "FONTSET_SST_STD_EUROPEAN_ITALIC", "SST-Italic.otf"},
|
|
{0x18170045, "FONTSET_SST_STD_EUROPEAN_MEDIUM_ITALIC", "SST-MediumItalic.otf"},
|
|
{0x18170047, "FONTSET_SST_STD_EUROPEAN_BOLD_ITALIC", "SST-BoldItalic.otf"},
|
|
{0x18170444, "FONTSET_SST_STD_EUROPEAN_JP_ITALIC", "SSTJpPro-Regular.otf"},
|
|
{0x18170447, "FONTSET_SST_STD_EUROPEAN_JP_BOLD_ITALIC", "SSTJpPro-Bold.otf"},
|
|
{0x18370044, "FONTSET_SST_TYPEWRITER_EUROPEAN", "SSTTypewriter-Roman.otf"},
|
|
{0x18370047, "FONTSET_SST_TYPEWRITER_EUROPEAN_BOLD", "SSTTypewriter-Bd.otf"},
|
|
{0x18370444, "FONTSET_SST_TYPEWRITER_EUROPEAN_JP", "SSTJpPro-Regular.otf"},
|
|
{0x18370447, "FONTSET_SST_TYPEWRITER_EUROPEAN_JP_BOLD", "SSTJpPro-Bold.otf"},
|
|
};
|
|
|
|
std::mutex g_fontset_cache_mutex;
|
|
std::unordered_map<u32, FontSetCache> g_fontset_cache;
|
|
|
|
const SystemFontDefinition* FindSystemFontDefinition(u32 font_set_type) {
|
|
for (const auto& def : kSystemFontDefinitions) {
|
|
if (def.font_set_type == font_set_type) {
|
|
return &def;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static bool DirectoryContainsAnyFontFiles(const std::filesystem::path& dir) {
|
|
std::error_code ec;
|
|
if (!std::filesystem::is_directory(dir, ec)) {
|
|
return false;
|
|
}
|
|
for (const auto& entry : std::filesystem::directory_iterator(dir, ec)) {
|
|
if (ec) {
|
|
return false;
|
|
}
|
|
if (!entry.is_regular_file(ec) || ec) {
|
|
continue;
|
|
}
|
|
const auto ext = entry.path().extension().string();
|
|
std::string lower;
|
|
lower.reserve(ext.size());
|
|
for (const char c : ext) {
|
|
lower.push_back(static_cast<char>(std::tolower(static_cast<unsigned char>(c))));
|
|
}
|
|
if (lower == ".otf" || lower == ".ttf" || lower == ".ttc" || lower == ".otc") {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static std::optional<std::filesystem::path> FindChildDirContainingFile(
|
|
const std::filesystem::path& base_dir, const std::string& filename) {
|
|
if (filename.empty()) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::error_code ec;
|
|
if (!std::filesystem::is_directory(base_dir, ec)) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<std::filesystem::path> match;
|
|
for (const auto& entry : std::filesystem::directory_iterator(base_dir, ec)) {
|
|
if (ec) {
|
|
return std::nullopt;
|
|
}
|
|
if (!entry.is_directory(ec) || ec) {
|
|
continue;
|
|
}
|
|
const auto candidate = entry.path() / filename;
|
|
if (std::filesystem::is_regular_file(candidate, ec) && !ec) {
|
|
if (match) {
|
|
return std::nullopt;
|
|
}
|
|
match = entry.path();
|
|
}
|
|
}
|
|
return match;
|
|
}
|
|
|
|
std::filesystem::path GetSysFontBaseDir() {
|
|
std::filesystem::path base = Config::getSysFontPath();
|
|
std::error_code ec;
|
|
if (base.empty()) {
|
|
LOG_ERROR(Lib_Font, "SystemFonts: SysFontPath not set");
|
|
return {};
|
|
}
|
|
if (std::filesystem::is_directory(base, ec)) {
|
|
if (DirectoryContainsAnyFontFiles(base)) {
|
|
return base;
|
|
}
|
|
|
|
{
|
|
const auto preferred = base / "font";
|
|
if (DirectoryContainsAnyFontFiles(preferred)) {
|
|
return preferred;
|
|
}
|
|
}
|
|
|
|
const std::string fallback = Config::getSystemFontFallbackName();
|
|
if (auto child = FindChildDirContainingFile(base, fallback)) {
|
|
return *child;
|
|
}
|
|
|
|
std::optional<std::filesystem::path> sole_font_dir;
|
|
for (const auto& entry : std::filesystem::directory_iterator(base, ec)) {
|
|
if (ec) {
|
|
break;
|
|
}
|
|
if (!entry.is_directory(ec) || ec) {
|
|
continue;
|
|
}
|
|
if (DirectoryContainsAnyFontFiles(entry.path())) {
|
|
if (sole_font_dir) {
|
|
sole_font_dir.reset();
|
|
break;
|
|
}
|
|
sole_font_dir = entry.path();
|
|
}
|
|
}
|
|
if (sole_font_dir) {
|
|
return *sole_font_dir;
|
|
}
|
|
|
|
LOG_ERROR(
|
|
Lib_Font,
|
|
"SystemFonts: SysFontPath '{}' contains no font files; set it to the directory that "
|
|
"contains the .otf/.ttf files (or ensure [SystemFonts].fallback is present in exactly "
|
|
"one child directory)",
|
|
base.string());
|
|
return {};
|
|
}
|
|
if (std::filesystem::is_regular_file(base, ec)) {
|
|
return base.parent_path();
|
|
}
|
|
LOG_ERROR(Lib_Font, "SystemFonts: SysFontPath '{}' is not a valid directory or file",
|
|
base.string());
|
|
return {};
|
|
}
|
|
|
|
static std::filesystem::path ResolveSystemFontPathCandidate(const std::filesystem::path& base_dir,
|
|
const std::filesystem::path& filename) {
|
|
if (base_dir.empty() || filename.empty()) {
|
|
return {};
|
|
}
|
|
std::error_code ec;
|
|
const auto is_file = [&](const std::filesystem::path& p) -> bool {
|
|
return std::filesystem::is_regular_file(p, ec) && !ec;
|
|
};
|
|
|
|
const auto direct = base_dir / filename;
|
|
if (is_file(direct)) {
|
|
return direct;
|
|
}
|
|
|
|
const auto base_name = base_dir.filename().string();
|
|
if (base_name != "font" && base_name != "font2") {
|
|
const auto in_font = base_dir / "font" / filename;
|
|
if (is_file(in_font)) {
|
|
return in_font;
|
|
}
|
|
const auto in_font2 = base_dir / "font2" / filename;
|
|
if (is_file(in_font2)) {
|
|
return in_font2;
|
|
}
|
|
}
|
|
|
|
if (base_name == "font" || base_name == "font2") {
|
|
const auto container = base_dir.parent_path();
|
|
const auto sibling = container / ((base_name == "font") ? "font2" : "font") / filename;
|
|
if (is_file(sibling)) {
|
|
return sibling;
|
|
}
|
|
}
|
|
|
|
return direct;
|
|
}
|
|
|
|
std::string MacroToCamel(const char* macro_key) {
|
|
if (!macro_key) {
|
|
return {};
|
|
}
|
|
std::string s(macro_key);
|
|
const std::string prefix = "FONTSET_";
|
|
if (s.rfind(prefix, 0) != 0) {
|
|
return {};
|
|
}
|
|
std::string out = "FontSet";
|
|
size_t pos = prefix.size();
|
|
while (pos < s.size()) {
|
|
size_t next = s.find('_', pos);
|
|
const size_t len = (next == std::string::npos) ? (s.size() - pos) : (next - pos);
|
|
std::string token = s.substr(pos, len);
|
|
for (auto& c : token) {
|
|
c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
|
}
|
|
if (!token.empty()) {
|
|
token[0] = static_cast<char>(std::toupper(static_cast<unsigned char>(token[0])));
|
|
}
|
|
out += token;
|
|
if (next == std::string::npos) {
|
|
break;
|
|
}
|
|
pos = next + 1;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
std::filesystem::path ResolveSystemFontPath(u32 font_set_type) {
|
|
if (const auto* def = FindSystemFontDefinition(font_set_type); def) {
|
|
const auto base_dir = GetSysFontBaseDir();
|
|
if (base_dir.empty()) {
|
|
return {};
|
|
}
|
|
if (auto override_path = Config::getSystemFontOverride(def->config_key)) {
|
|
if (!override_path->empty() && !override_path->is_absolute() &&
|
|
!override_path->has_parent_path()) {
|
|
return ResolveSystemFontPathCandidate(base_dir, *override_path);
|
|
}
|
|
LOG_ERROR(Lib_Font,
|
|
"SystemFonts: override for '{}' must be a filename only (no path): '{}'",
|
|
def->config_key, override_path->string());
|
|
}
|
|
const auto camel_key = MacroToCamel(def->config_key);
|
|
if (!camel_key.empty()) {
|
|
if (auto override_path2 = Config::getSystemFontOverride(camel_key)) {
|
|
if (!override_path2->empty() && !override_path2->is_absolute() &&
|
|
!override_path2->has_parent_path()) {
|
|
return ResolveSystemFontPathCandidate(base_dir, *override_path2);
|
|
}
|
|
LOG_ERROR(Lib_Font,
|
|
"SystemFonts: override for '{}' must be a filename only (no path): '{}'",
|
|
camel_key, override_path2->string());
|
|
}
|
|
std::string lower_camel = camel_key;
|
|
lower_camel[0] =
|
|
static_cast<char>(std::tolower(static_cast<unsigned char>(lower_camel[0])));
|
|
if (auto override_path3 = Config::getSystemFontOverride(lower_camel)) {
|
|
if (!override_path3->empty() && !override_path3->is_absolute() &&
|
|
!override_path3->has_parent_path()) {
|
|
return ResolveSystemFontPathCandidate(base_dir, *override_path3);
|
|
}
|
|
LOG_ERROR(Lib_Font,
|
|
"SystemFonts: override for '{}' must be a filename only (no path): '{}'",
|
|
lower_camel, override_path3->string());
|
|
}
|
|
}
|
|
if (def->default_file && *def->default_file) {
|
|
return ResolveSystemFontPathCandidate(base_dir, def->default_file);
|
|
}
|
|
}
|
|
LOG_ERROR(Lib_Font, "SystemFonts: unknown font set type=0x{:08X}", font_set_type);
|
|
return {};
|
|
}
|
|
|
|
std::filesystem::path ResolveSystemFontPathFromConfigOnly(u32 font_set_type) {
|
|
const auto base_dir = GetSysFontBaseDir();
|
|
if (base_dir.empty()) {
|
|
return {};
|
|
}
|
|
|
|
const std::string key_a = fmt::format("fontset_0x{:08X}", font_set_type);
|
|
const std::string key_b = fmt::format("fontSet_0x{:08X}", font_set_type);
|
|
|
|
auto try_key = [&](const std::string& key) -> std::optional<std::filesystem::path> {
|
|
if (auto override_path = Config::getSystemFontOverride(key)) {
|
|
if (!override_path->empty() && override_path->is_absolute()) {
|
|
return *override_path;
|
|
}
|
|
if (!override_path->empty() && !override_path->has_parent_path()) {
|
|
return base_dir / *override_path;
|
|
}
|
|
LOG_ERROR(
|
|
Lib_Font,
|
|
"SystemFonts: override for '{}' must be a filename only or absolute path: '{}'",
|
|
key, override_path->string());
|
|
}
|
|
return std::nullopt;
|
|
};
|
|
|
|
if (auto p = try_key(key_a)) {
|
|
return *p;
|
|
}
|
|
if (auto p = try_key(key_b)) {
|
|
return *p;
|
|
}
|
|
|
|
const std::string fallback = Config::getSystemFontFallbackName();
|
|
if (!fallback.empty()) {
|
|
return base_dir / fallback;
|
|
}
|
|
return {};
|
|
}
|
|
|
|
const FontSetCache* EnsureFontSetCache(u32 font_set_type) {
|
|
if (font_set_type == 0) {
|
|
return nullptr;
|
|
}
|
|
std::scoped_lock lock(g_fontset_cache_mutex);
|
|
auto& cache = g_fontset_cache[font_set_type];
|
|
if (!cache.loaded) {
|
|
cache.loaded = true;
|
|
const auto path = ResolveSystemFontPath(font_set_type);
|
|
if (!path.empty() && LoadFontFile(path, cache.bytes)) {
|
|
cache.available = HasSfntTables(cache.bytes);
|
|
cache.path = cache.available ? path : std::filesystem::path{};
|
|
} else {
|
|
cache.available = false;
|
|
LOG_ERROR(Lib_Font, "SystemFonts: failed to load font for set type=0x{:08X} path='{}'",
|
|
font_set_type, path.string());
|
|
}
|
|
}
|
|
return cache.available ? &cache : nullptr;
|
|
}
|
|
|
|
bool HasSfntTables(const std::vector<unsigned char>& bytes) {
|
|
if (bytes.size() < 12) {
|
|
return false;
|
|
}
|
|
const u8* ptr = bytes.data();
|
|
const u32 num_tables = (ptr[4] << 8) | ptr[5];
|
|
constexpr u32 kDirEntrySize = 16;
|
|
if (bytes.size() < 12 + static_cast<size_t>(num_tables) * kDirEntrySize) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool LoadFontFile(const std::filesystem::path& path, std::vector<unsigned char>& out_bytes) {
|
|
if (path.empty()) {
|
|
return false;
|
|
}
|
|
|
|
auto try_load = [&](const std::filesystem::path& p) -> bool {
|
|
out_bytes.clear();
|
|
if (!LoadGuestFileBytes(p, out_bytes) || out_bytes.empty()) {
|
|
out_bytes.clear();
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
if (try_load(path)) {
|
|
return true;
|
|
}
|
|
|
|
std::error_code ec;
|
|
const auto parent = path.parent_path();
|
|
const auto parent_name = parent.filename().string();
|
|
const auto file_name = path.filename();
|
|
if (!file_name.empty() && parent_name != "font" && parent_name != "font2") {
|
|
const auto cand_font = parent / "font" / file_name;
|
|
if (std::filesystem::is_regular_file(cand_font, ec) && !ec) {
|
|
if (try_load(cand_font)) {
|
|
return true;
|
|
}
|
|
}
|
|
const auto cand_font2 = parent / "font2" / file_name;
|
|
if (std::filesystem::is_regular_file(cand_font2, ec) && !ec) {
|
|
if (try_load(cand_font2)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!file_name.empty() && (parent_name == "font" || parent_name == "font2")) {
|
|
const auto container = parent.parent_path();
|
|
const auto sibling = container / ((parent_name == "font") ? "font2" : "font") / file_name;
|
|
if (std::filesystem::is_regular_file(sibling, ec) && !ec) {
|
|
return try_load(sibling);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool AttachSystemFont(FontState& st, Libraries::Font::OrbisFontHandle handle) {
|
|
if (st.ext_face_ready) {
|
|
return true;
|
|
}
|
|
st.system_fallback_faces.clear();
|
|
st.system_font_id = 0;
|
|
st.system_font_scale_factor = 1.0f;
|
|
st.system_font_shift_value = 0;
|
|
|
|
const auto* native = reinterpret_cast<const Libraries::Font::FontHandleOpaqueNative*>(handle);
|
|
const u32 subfont_index = native ? native->open_info.sub_font_index : 0u;
|
|
auto* lib = static_cast<FontLibOpaque*>(st.library);
|
|
if (!lib || lib->magic != 0x0F01 || st.font_set_type == 0) {
|
|
return false;
|
|
}
|
|
|
|
std::filesystem::path primary_path = ResolveSystemFontPathFromConfigOnly(st.font_set_type);
|
|
if (primary_path.empty()) {
|
|
primary_path = ResolveSystemFontPath(st.font_set_type);
|
|
}
|
|
std::vector<unsigned char> primary_bytes;
|
|
if (primary_path.empty() || !LoadFontFile(primary_path, primary_bytes)) {
|
|
return false;
|
|
}
|
|
|
|
DestroyFreeTypeFace(st.ext_ft_face);
|
|
st.ext_face_data = std::move(primary_bytes);
|
|
st.ext_ft_face = CreateFreeTypeFaceFromBytes(st.ext_face_data.data(), st.ext_face_data.size(),
|
|
subfont_index);
|
|
if (!st.ext_ft_face) {
|
|
st.ext_face_data.clear();
|
|
return false;
|
|
}
|
|
|
|
st.ext_cache.clear();
|
|
st.scratch.clear();
|
|
st.logged_ext_use = false;
|
|
st.ext_scale_for_height = 0.0f;
|
|
st.layout_cached = false;
|
|
const FaceMetrics m = LoadFaceMetrics(st.ext_ft_face);
|
|
PopulateStateMetrics(st, m);
|
|
st.ext_face_ready = true;
|
|
st.system_font_path = primary_path;
|
|
st.system_requested = true;
|
|
|
|
const auto base_dir = GetSysFontBaseDir();
|
|
if (!base_dir.empty()) {
|
|
auto resolve_override =
|
|
[&](const std::string& key) -> std::optional<std::filesystem::path> {
|
|
if (auto override_path = Config::getSystemFontOverride(key)) {
|
|
if (!override_path->empty() && override_path->is_absolute()) {
|
|
return *override_path;
|
|
}
|
|
if (!override_path->empty() && !override_path->has_parent_path()) {
|
|
return base_dir / *override_path;
|
|
}
|
|
}
|
|
return std::nullopt;
|
|
};
|
|
|
|
for (int i = 0; i < 8; ++i) {
|
|
const std::string key_a =
|
|
fmt::format("fontset_0x{:08X}_fallback{}", st.font_set_type, i);
|
|
const std::string key_b =
|
|
fmt::format("fontSet_0x{:08X}_fallback{}", st.font_set_type, i);
|
|
std::optional<std::filesystem::path> p = resolve_override(key_a);
|
|
if (!p) {
|
|
p = resolve_override(key_b);
|
|
}
|
|
if (!p || p->empty()) {
|
|
continue;
|
|
}
|
|
|
|
std::vector<unsigned char> fb_bytes;
|
|
if (!LoadFontFile(*p, fb_bytes)) {
|
|
continue;
|
|
}
|
|
FontState::SystemFallbackFace fb{};
|
|
fb.font_id = static_cast<u32>(i + 1);
|
|
fb.scale_factor = 1.0f;
|
|
fb.shift_value = 0;
|
|
fb.path = *p;
|
|
fb.bytes = std::make_shared<std::vector<unsigned char>>(std::move(fb_bytes));
|
|
fb.ft_face =
|
|
CreateFreeTypeFaceFromBytes(fb.bytes->data(), fb.bytes->size(), subfont_index);
|
|
fb.ready = (fb.ft_face != nullptr);
|
|
if (fb.ready) {
|
|
st.system_fallback_faces.push_back(std::move(fb));
|
|
} else {
|
|
DestroyFreeTypeFace(fb.ft_face);
|
|
}
|
|
}
|
|
|
|
const std::string global_fallback_name = Config::getSystemFontFallbackName();
|
|
if (!global_fallback_name.empty()) {
|
|
const auto existing_name = primary_path.filename().string();
|
|
if (existing_name != global_fallback_name) {
|
|
const std::filesystem::path fb_path = base_dir / global_fallback_name;
|
|
std::vector<unsigned char> fb_bytes;
|
|
if (LoadFontFile(fb_path, fb_bytes)) {
|
|
FontState::SystemFallbackFace fb{};
|
|
fb.font_id = 0xFFFFFFFFu;
|
|
fb.scale_factor = 1.0f;
|
|
fb.shift_value = 0;
|
|
fb.path = fb_path;
|
|
fb.bytes = std::make_shared<std::vector<unsigned char>>(std::move(fb_bytes));
|
|
fb.ft_face = CreateFreeTypeFaceFromBytes(fb.bytes->data(), fb.bytes->size(),
|
|
subfont_index);
|
|
fb.ready = (fb.ft_face != nullptr);
|
|
if (fb.ready) {
|
|
st.system_fallback_faces.push_back(std::move(fb));
|
|
} else {
|
|
DestroyFreeTypeFace(fb.ft_face);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
LOG_INFO(Lib_Font, "system font attached");
|
|
LOG_DEBUG(Lib_Font,
|
|
"system font attach params:\n"
|
|
" handle={}\n"
|
|
" font_set_type=0x{:08X}\n"
|
|
" path={}\n",
|
|
static_cast<const void*>(handle), st.font_set_type, st.system_font_path.string());
|
|
return true;
|
|
}
|
|
|
|
std::string ReportSystemFaceRequest(FontState& st, Libraries::Font::OrbisFontHandle handle,
|
|
bool& attached_out) {
|
|
attached_out = AttachSystemFont(st, handle);
|
|
if (attached_out) {
|
|
st.system_requested = true;
|
|
return {};
|
|
}
|
|
if (!st.system_requested) {
|
|
st.system_requested = true;
|
|
const auto configured = Config::getSysFontPath();
|
|
return fmt::format("SystemFace: handle={} requested internal font but sysFontPath ('{}') "
|
|
"could not be loaded",
|
|
static_cast<const void*>(handle), configured.string());
|
|
}
|
|
return {};
|
|
}
|
|
} // namespace Libraries::Font::Internal
|