mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-12-16 04:09:07 +00:00
487 lines
12 KiB
C++
487 lines
12 KiB
C++
#include "stdafx.h"
|
|
#include "overlay_fonts.h"
|
|
#include "Emu/System.h"
|
|
#include "Emu/vfs_config.h"
|
|
|
|
#ifndef _WIN32
|
|
#include <unistd.h>
|
|
#include <sys/types.h>
|
|
#include <pwd.h>
|
|
|
|
#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__)
|
|
#include <sys/sysctl.h>
|
|
#endif
|
|
#endif
|
|
|
|
namespace rsx
|
|
{
|
|
namespace overlays
|
|
{
|
|
void codepage::initialize_glyphs(char32_t codepage_id, f32 font_size, const std::vector<u8>& ttf_data)
|
|
{
|
|
glyph_base = (codepage_id * 256);
|
|
glyph_data.resize(bitmap_width * bitmap_height);
|
|
pack_info.resize(256);
|
|
|
|
stbtt_pack_context context;
|
|
if (!stbtt_PackBegin(&context, glyph_data.data(), bitmap_width, bitmap_height, 0, 1, nullptr))
|
|
{
|
|
rsx_log.error("Font packing failed");
|
|
return;
|
|
}
|
|
|
|
stbtt_PackSetOversampling(&context, oversample, oversample);
|
|
|
|
if (!stbtt_PackFontRange(&context, ttf_data.data(), 0, font_size, (codepage_id * 256), 256, pack_info.data()))
|
|
{
|
|
rsx_log.error("Font packing failed");
|
|
stbtt_PackEnd(&context);
|
|
return;
|
|
}
|
|
|
|
stbtt_PackEnd(&context);
|
|
}
|
|
|
|
stbtt_aligned_quad codepage::get_char(char32_t c, f32& x_advance, f32& y_advance)
|
|
{
|
|
stbtt_aligned_quad quad;
|
|
stbtt_GetPackedQuad(pack_info.data(), bitmap_width, bitmap_height, (c - glyph_base), &x_advance, &y_advance, &quad, false);
|
|
|
|
quad.t0 += sampler_z;
|
|
quad.t1 += sampler_z;
|
|
return quad;
|
|
}
|
|
|
|
font::font(std::string_view ttf_name, f32 size)
|
|
{
|
|
// Convert pt to px
|
|
size_px = ceilf(size * 96.f / 72.f);
|
|
size_pt = size;
|
|
|
|
font_name = std::string(ttf_name);
|
|
initialized = true;
|
|
}
|
|
|
|
language_class font::classify(char32_t codepage_id)
|
|
{
|
|
switch (codepage_id)
|
|
{
|
|
case 00: // Extended ASCII
|
|
case 04: // Cyrillic
|
|
{
|
|
return language_class::default_;
|
|
}
|
|
case 0x11: // Hangul jamo
|
|
case 0x31: // Compatibility jamo 3130-318F
|
|
// case 0xA9: // Hangul jamo extended block A A960-A97F
|
|
{
|
|
return language_class::hangul;
|
|
}
|
|
case 0xFF: // Halfwidth and Fullwidth Forms
|
|
{
|
|
// Found in SCE-PS3-SR-R-JPN.TTF, so we'll use cjk_base for now
|
|
return language_class::cjk_base;
|
|
}
|
|
default:
|
|
{
|
|
if (codepage_id >= 0xAC && codepage_id <= 0xD7)
|
|
{
|
|
// Hangul syllables + jamo extended block B
|
|
return language_class::hangul;
|
|
}
|
|
|
|
if (codepage_id >= 0x2E && codepage_id <= 0x9F)
|
|
{
|
|
// Generic CJK blocks, mostly chinese and japanese kana
|
|
// Should typically be compatible with JPN ttf fonts
|
|
return language_class::cjk_base;
|
|
}
|
|
|
|
// TODO
|
|
return language_class::default_;
|
|
}
|
|
}
|
|
}
|
|
|
|
glyph_load_setup font::get_glyph_files(language_class class_) const
|
|
{
|
|
glyph_load_setup result;
|
|
result.font_names.push_back(font_name);
|
|
|
|
const std::vector<std::string> font_dirs = Emu.GetCallbacks().get_font_dirs();
|
|
result.lookup_font_dirs.insert(result.lookup_font_dirs.end(), font_dirs.begin(), font_dirs.end());
|
|
// Search dev_flash for the font too
|
|
result.lookup_font_dirs.push_back(g_cfg_vfs.get_dev_flash() + "data/font/");
|
|
result.lookup_font_dirs.push_back(g_cfg_vfs.get_dev_flash() + "data/font/SONY-CC/");
|
|
|
|
switch (class_)
|
|
{
|
|
case language_class::default_:
|
|
{
|
|
result.font_names.emplace_back("Arial.ttf");
|
|
result.font_names.emplace_back("arial.ttf");
|
|
#ifdef __APPLE__
|
|
result.font_names.emplace_back("DejaVuSans.ttf");
|
|
result.font_names.emplace_back("NotoSans-Regular.ttf");
|
|
result.font_names.emplace_back("Roboto-Regular.ttf");
|
|
result.font_names.emplace_back("OpenSans-Regular.ttf");
|
|
result.font_names.emplace_back("FreeSans.ttf");
|
|
#elif !defined(_WIN32)
|
|
result.font_names.emplace_back("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"); // ubuntu
|
|
result.font_names.emplace_back("/usr/share/fonts/TTF/DejaVuSans.ttf"); // arch
|
|
#endif
|
|
// Attempt to load a font from dev_flash as a last resort
|
|
result.font_names.emplace_back("SCE-PS3-VR-R-LATIN.TTF");
|
|
break;
|
|
}
|
|
case language_class::cjk_base:
|
|
{
|
|
// Skip loading font files directly
|
|
result.font_names.clear();
|
|
|
|
// Attempt to load a font from dev_flash before any other source
|
|
result.font_names.emplace_back("SCE-PS3-SR-R-JPN.TTF");
|
|
result.font_names.emplace_back("SCE-PS3-DH-R-CGB.TTF");
|
|
|
|
// Known system font as last fallback
|
|
result.font_names.emplace_back("Yu Gothic.ttf");
|
|
result.font_names.emplace_back("YuGothR.ttc");
|
|
#ifdef _WIN32
|
|
result.font_names.emplace_back("msyh.ttc");
|
|
result.font_names.emplace_back("simsunb.ttc");
|
|
result.font_names.emplace_back("simsun.ttc");
|
|
result.font_names.emplace_back("SimsunExtG.ttf");
|
|
#endif
|
|
break;
|
|
}
|
|
case language_class::hangul:
|
|
{
|
|
// Skip loading font files directly
|
|
result.font_names.clear();
|
|
|
|
// Attempt to load a font from dev_flash before any other source
|
|
result.font_names.emplace_back("SCE-PS3-YG-R-KOR.TTF");
|
|
|
|
// Known system font as last fallback
|
|
result.font_names.emplace_back("Malgun Gothic.ttf");
|
|
result.font_names.emplace_back("malgun.ttf");
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
codepage* font::initialize_codepage(char32_t c)
|
|
{
|
|
// Init glyph
|
|
const auto codepage_id = get_page_id(c);
|
|
const auto class_ = classify(codepage_id);
|
|
const auto fs_settings = get_glyph_files(class_);
|
|
|
|
// Attemt to load requested font
|
|
std::vector<u8> bytes;
|
|
std::vector<u8> fallback_bytes;
|
|
std::string fallback_file;
|
|
bool font_found = false;
|
|
|
|
const auto get_font = [&](const std::string& file_path) -> bool
|
|
{
|
|
// Read font
|
|
fs::file f(file_path);
|
|
f.read(bytes, f.size());
|
|
|
|
// Check if the character exists in the font
|
|
stbtt_fontinfo info;
|
|
if (stbtt_InitFont(&info, bytes.data(), stbtt_GetFontOffsetForIndex(bytes.data(), 0)) != 0)
|
|
{
|
|
font_found = stbtt_FindGlyphIndex(&info, c) != 0;
|
|
}
|
|
|
|
if (!font_found)
|
|
{
|
|
if (fallback_bytes.empty())
|
|
{
|
|
// Save this font as a fallback so we don't get a segfault or exception
|
|
fallback_bytes = std::move(bytes);
|
|
fallback_file = file_path;
|
|
}
|
|
|
|
bytes.clear();
|
|
}
|
|
|
|
return font_found;
|
|
};
|
|
|
|
for (const auto& font_file : fs_settings.font_names)
|
|
{
|
|
if (fs::is_file(font_file))
|
|
{
|
|
// Check for absolute paths or fonts 'installed' to executable folder
|
|
if (get_font(font_file))
|
|
{
|
|
break;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
std::string extension;
|
|
if (const auto extension_start = font_file.find_last_of('.');
|
|
extension_start != umax)
|
|
{
|
|
extension = font_file.substr(extension_start + 1);
|
|
}
|
|
|
|
std::string file_name = font_file;
|
|
if (extension.length() != 3)
|
|
{
|
|
// Allow other extensions to support other truetype formats
|
|
file_name += ".ttf";
|
|
}
|
|
|
|
for (const auto& font_dir : fs_settings.lookup_font_dirs)
|
|
{
|
|
const std::string file_path = font_dir + file_name;
|
|
if (fs::is_file(file_path))
|
|
{
|
|
if (get_font(file_path))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (font_found)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!font_found)
|
|
{
|
|
if (fallback_bytes.empty())
|
|
{
|
|
fmt::throw_exception("Failed to initialize font for character 0x%x on codepage %d.", static_cast<u32>(c), static_cast<u32>(codepage_id));
|
|
}
|
|
|
|
rsx_log.error("Failed to initialize font for character 0x%x on codepage %d. Falling back to font '%s'", static_cast<u32>(c), static_cast<u32>(codepage_id), fallback_file);
|
|
bytes = std::move(fallback_bytes);
|
|
}
|
|
|
|
codepage_cache.page = nullptr;
|
|
auto page = std::make_unique<codepage>();
|
|
page->initialize_glyphs(codepage_id, size_px, bytes);
|
|
page->sampler_z = static_cast<f32>(m_glyph_map.size());
|
|
|
|
auto ret = page.get();
|
|
m_glyph_map.emplace_back(codepage_id, std::move(page));
|
|
|
|
if (codepage_id == 0)
|
|
{
|
|
// Latin-1
|
|
f32 unused;
|
|
get_char('m', em_size, unused);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
stbtt_aligned_quad font::get_char(char32_t c, f32& x_advance, f32& y_advance)
|
|
{
|
|
if (!initialized)
|
|
return {};
|
|
|
|
const auto page_id = get_page_id(c);
|
|
|
|
if (codepage_cache.codepage_id == page_id && codepage_cache.page) [[likely]]
|
|
{
|
|
return codepage_cache.page->get_char(c, x_advance, y_advance);
|
|
}
|
|
else
|
|
{
|
|
codepage_cache.codepage_id = page_id;
|
|
codepage_cache.page = nullptr;
|
|
|
|
for (const auto& e : m_glyph_map)
|
|
{
|
|
if (e.first == page_id)
|
|
{
|
|
codepage_cache.page = e.second.get();
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!codepage_cache.page) [[unlikely]]
|
|
{
|
|
codepage_cache.page = initialize_codepage(c);
|
|
}
|
|
|
|
return codepage_cache.page->get_char(c, x_advance, y_advance);
|
|
}
|
|
}
|
|
|
|
std::vector<vertex> font::render_text_ex(f32& x_advance, f32& y_advance, const char32_t* text, usz char_limit, u16 max_width, bool wrap)
|
|
{
|
|
x_advance = 0.f;
|
|
y_advance = 0.f;
|
|
std::vector<vertex> result;
|
|
|
|
if (!initialized)
|
|
{
|
|
return result;
|
|
}
|
|
|
|
// Render as many characters as possible as glyphs.
|
|
for (usz i = 0u, begin_of_word = 0u; i < char_limit; i++)
|
|
{
|
|
switch (const auto& c = text[i])
|
|
{
|
|
case '\0':
|
|
{
|
|
// We're done.
|
|
return result;
|
|
}
|
|
case '\n':
|
|
{
|
|
// Reset x to 0 and increase y to advance to the new line.
|
|
x_advance = 0.f;
|
|
y_advance += size_px + 2.f;
|
|
begin_of_word = result.size();
|
|
continue;
|
|
}
|
|
case '\r':
|
|
{
|
|
// Reset x to 0.
|
|
x_advance = 0.f;
|
|
begin_of_word = result.size();
|
|
continue;
|
|
}
|
|
default:
|
|
{
|
|
const bool is_whitespace = c == ' ';
|
|
stbtt_aligned_quad quad{};
|
|
|
|
if (is_whitespace)
|
|
{
|
|
// Skip whitespace if we are at the start of a line.
|
|
if (x_advance <= 0.f)
|
|
{
|
|
// Set the glyph to the current position.
|
|
// This is necessary for downstream linewidth calculations.
|
|
quad.x0 = quad.x1 = x_advance;
|
|
quad.y0 = quad.y1 = y_advance;
|
|
}
|
|
else
|
|
{
|
|
const f32 x_advance_old = x_advance;
|
|
const f32 y_advance_old = y_advance;
|
|
|
|
// Get the glyph size.
|
|
quad = get_char(c, x_advance, y_advance);
|
|
|
|
// Reset the result if the glyph would protrude out of the given space anyway.
|
|
if (x_advance > max_width)
|
|
{
|
|
// Set the glyph to the previous position.
|
|
// This is necessary for downstream linewidth calculations.
|
|
quad.x0 = quad.x1 = x_advance_old;
|
|
quad.y0 = quad.y1 = y_advance_old;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// No whitespace. Get the glyph size.
|
|
quad = get_char(c, x_advance, y_advance);
|
|
}
|
|
|
|
// Add the glyph's vertices.
|
|
result.emplace_back(quad.x0, quad.y0, quad.s0, quad.t0);
|
|
result.emplace_back(quad.x1, quad.y0, quad.s1, quad.t0);
|
|
result.emplace_back(quad.x0, quad.y1, quad.s0, quad.t1);
|
|
result.emplace_back(quad.x1, quad.y1, quad.s1, quad.t1);
|
|
|
|
// The next word will begin after any whitespaces.
|
|
if (is_whitespace)
|
|
{
|
|
begin_of_word = result.size();
|
|
}
|
|
|
|
// Check if we reached the end of the available space.
|
|
if (x_advance > max_width)
|
|
{
|
|
// Try to wrap the protruding text
|
|
if (wrap)
|
|
{
|
|
// Increase y to advance to the next line.
|
|
y_advance += size_px + 2.f;
|
|
|
|
// We can just reset x and move on to the next character if this is a whitespace.
|
|
if (is_whitespace)
|
|
{
|
|
x_advance = 0.f;
|
|
break;
|
|
}
|
|
|
|
// Get the leftmost offset of the current word.
|
|
const f32 base_x = result[begin_of_word].x();
|
|
|
|
// Move all characters of the current word one line down and to the left.
|
|
for (usz n = begin_of_word; n < result.size(); ++n)
|
|
{
|
|
result[n].x() -= base_x;
|
|
result[n].y() += size_px + 2.f;
|
|
}
|
|
|
|
// Set x offset to the rightmost position of the current word
|
|
x_advance = result.back().x();
|
|
}
|
|
else
|
|
{
|
|
// TODO: Ellipsize
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
} // switch
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::vector<vertex> font::render_text(const char32_t* text, u16 max_width, bool wrap)
|
|
{
|
|
f32 unused_x, unused_y;
|
|
|
|
return render_text_ex(unused_x, unused_y, text, -1, max_width, wrap);
|
|
}
|
|
|
|
std::pair<f32, f32> font::get_char_offset(const char32_t* text, usz max_length, u16 max_width, bool wrap)
|
|
{
|
|
f32 loc_x, loc_y;
|
|
|
|
render_text_ex(loc_x, loc_y, text, max_length, max_width, wrap);
|
|
return {loc_x, loc_y};
|
|
}
|
|
|
|
const std::vector<u8>& font::get_glyph_data() const
|
|
{
|
|
constexpr u32 page_size = codepage::bitmap_width * codepage::bitmap_height;
|
|
const auto size = page_size * m_glyph_map.size();
|
|
|
|
static thread_local std::vector<u8> bytes;
|
|
bytes.resize(size);
|
|
|
|
u8* data = bytes.data();
|
|
|
|
for (const auto& e : m_glyph_map)
|
|
{
|
|
std::memcpy(data, e.second->glyph_data.data(), page_size);
|
|
data += page_size;
|
|
}
|
|
return bytes;
|
|
}
|
|
} // namespace overlays
|
|
} // namespace rsx
|