2020-09-21 17:17:34 +00:00
|
|
|
// SPDX-License-Identifier: MIT
|
2020-06-21 12:25:28 +00:00
|
|
|
//
|
2020-09-21 17:17:34 +00:00
|
|
|
// EmulationStation Desktop Edition
|
2020-06-21 12:25:28 +00:00
|
|
|
// Font.h
|
|
|
|
//
|
|
|
|
// Loading, unloading, caching and rendering of fonts.
|
|
|
|
// Also functions for word wrapping and similar.
|
|
|
|
//
|
|
|
|
|
2014-06-20 01:30:09 +00:00
|
|
|
#include "resources/Font.h"
|
2017-11-01 22:21:10 +00:00
|
|
|
|
2019-08-08 20:16:11 +00:00
|
|
|
#include "renderers/Renderer.h"
|
2018-01-26 18:53:19 +00:00
|
|
|
#include "utils/FileSystemUtil.h"
|
2017-11-15 15:59:39 +00:00
|
|
|
#include "utils/StringUtil.h"
|
2014-06-20 01:30:09 +00:00
|
|
|
#include "Log.h"
|
2019-08-08 20:16:11 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
FT_Library Font::sLibrary = nullptr;
|
2013-10-04 23:24:41 +00:00
|
|
|
|
|
|
|
int Font::getSize() const { return mSize; }
|
|
|
|
|
2020-08-15 07:28:47 +00:00
|
|
|
std::map<std::pair<std::string, int>, std::weak_ptr<Font>> Font::sFontMap;
|
2013-10-04 23:24:41 +00:00
|
|
|
|
2014-08-30 20:36:56 +00:00
|
|
|
Font::FontFace::FontFace(ResourceData&& d, int size) : data(d)
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
int err = FT_New_Memory_Face(sLibrary, data.ptr.get(), (FT_Long)data.length, 0, &face);
|
|
|
|
assert(!err);
|
2019-08-25 15:23:02 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
if (!err)
|
|
|
|
FT_Set_Pixel_Sizes(face, 0, size);
|
2014-08-30 20:36:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Font::FontFace::~FontFace()
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
if (face)
|
|
|
|
FT_Done_Face(face);
|
2014-08-30 20:36:56 +00:00
|
|
|
}
|
|
|
|
|
2013-10-04 23:24:41 +00:00
|
|
|
void Font::initLibrary()
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
assert(sLibrary == nullptr);
|
|
|
|
if (FT_Init_FreeType(&sLibrary)) {
|
|
|
|
sLibrary = nullptr;
|
2021-01-13 18:48:31 +00:00
|
|
|
LOG(LogError) << "Couldn't initialize FreeType";
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
2013-10-04 23:24:41 +00:00
|
|
|
}
|
|
|
|
|
2014-03-27 21:47:25 +00:00
|
|
|
size_t Font::getMemUsage() const
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
size_t memUsage = 0;
|
|
|
|
for (auto it = mTextures.cbegin(); it != mTextures.cend(); it++)
|
|
|
|
memUsage += it->textureSize.x() * it->textureSize.y() * 4;
|
2014-03-27 21:47:25 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
for (auto it = mFaceCache.cbegin(); it != mFaceCache.cend(); it++)
|
|
|
|
memUsage += it->second->data.length;
|
2014-08-30 20:36:56 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
return memUsage;
|
2014-03-27 21:47:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
size_t Font::getTotalMemUsage()
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
size_t total = 0;
|
2014-03-27 21:47:25 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
auto it = sFontMap.cbegin();
|
|
|
|
while (it != sFontMap.cend()) {
|
|
|
|
if (it->second.expired()) {
|
|
|
|
it = sFontMap.erase(it);
|
|
|
|
continue;
|
|
|
|
}
|
2014-03-27 21:47:25 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
total += it->second.lock()->getMemUsage();
|
|
|
|
it++;
|
|
|
|
}
|
2014-03-27 21:47:25 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
return total;
|
2014-03-27 21:47:25 +00:00
|
|
|
}
|
|
|
|
|
2014-07-27 21:44:02 +00:00
|
|
|
Font::Font(int size, const std::string& path) : mSize(size), mPath(path)
|
2013-10-04 23:24:41 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
assert(mSize > 0);
|
2019-08-25 15:23:02 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
mMaxGlyphHeight = 0;
|
2014-09-27 21:09:49 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
if (!sLibrary)
|
|
|
|
initLibrary();
|
2014-07-27 21:44:02 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// Always initialize ASCII characters.
|
|
|
|
for (unsigned int i = 32; i < 128; i++)
|
|
|
|
getGlyph(i);
|
2014-08-30 20:36:56 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
clearFaceCache();
|
2013-10-04 23:24:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Font::~Font()
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
unload(ResourceManager::getInstance());
|
2013-10-04 23:24:41 +00:00
|
|
|
}
|
|
|
|
|
2017-11-17 14:58:52 +00:00
|
|
|
void Font::reload(std::shared_ptr<ResourceManager>& /*rm*/)
|
2013-10-04 23:24:41 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
rebuildTextures();
|
2013-10-04 23:24:41 +00:00
|
|
|
}
|
|
|
|
|
2017-11-17 14:58:52 +00:00
|
|
|
void Font::unload(std::shared_ptr<ResourceManager>& /*rm*/)
|
2013-10-04 23:24:41 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
unloadTextures();
|
2013-10-04 23:24:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
std::shared_ptr<Font> Font::get(int size, const std::string& path)
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
const std::string canonicalPath = Utils::FileSystem::getCanonicalPath(path);
|
2014-05-23 22:21:01 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
std::pair<std::string, int> def(canonicalPath.empty() ? getDefaultPath() : canonicalPath, size);
|
|
|
|
auto foundFont = sFontMap.find(def);
|
|
|
|
if (foundFont != sFontMap.cend()) {
|
|
|
|
if (!foundFont->second.expired())
|
|
|
|
return foundFont->second.lock();
|
|
|
|
}
|
2013-10-04 23:24:41 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
std::shared_ptr<Font> font = std::shared_ptr<Font>(new Font(def.second, def.first));
|
|
|
|
sFontMap[def] = std::weak_ptr<Font>(font);
|
|
|
|
ResourceManager::getInstance()->addReloadable(font);
|
|
|
|
return font;
|
2013-10-04 23:24:41 +00:00
|
|
|
}
|
|
|
|
|
2014-08-11 23:05:18 +00:00
|
|
|
void Font::unloadTextures()
|
2013-10-04 23:24:41 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
for (auto it = mTextures.begin(); it != mTextures.end(); it++)
|
|
|
|
it->deinitTexture();
|
2014-08-11 23:05:18 +00:00
|
|
|
}
|
2014-07-27 21:44:02 +00:00
|
|
|
|
2021-01-16 17:05:48 +00:00
|
|
|
Font::FontTexture::FontTexture(const int mSize)
|
2014-08-11 23:05:18 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
textureId = 0;
|
2021-01-16 17:05:48 +00:00
|
|
|
// I'm not entirely sure if the 16 and 4 constants are correct, but they seem to provide
|
|
|
|
// a texture buffer large enough to hold the fonts. (Otherwise the application would crash.)
|
|
|
|
textureSize = Vector2i(mSize * 16, mSize * 4);
|
2020-06-21 12:25:28 +00:00
|
|
|
writePos = Vector2i::Zero();
|
|
|
|
rowHeight = 0;
|
2014-08-11 23:05:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Font::FontTexture::~FontTexture()
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
deinitTexture();
|
2013-10-04 23:24:41 +00:00
|
|
|
}
|
|
|
|
|
2017-10-28 20:24:35 +00:00
|
|
|
bool Font::FontTexture::findEmpty(const Vector2i& size, Vector2i& cursor_out)
|
2014-07-27 21:44:02 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
if (size.x() >= textureSize.x() || size.y() >= textureSize.y())
|
|
|
|
return false;
|
2014-05-12 22:05:28 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
if (writePos.x() + size.x() >= textureSize.x() &&
|
|
|
|
writePos.y() + rowHeight + size.y() + 1 < textureSize.y()) {
|
|
|
|
// Row full, but it should fit on the next row.
|
|
|
|
// Move cursor to next row.
|
|
|
|
writePos = Vector2i(0, writePos.y() + rowHeight + 1); // Leave 1px of space between glyphs.
|
|
|
|
rowHeight = 0;
|
|
|
|
}
|
2013-10-04 23:24:41 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
if (writePos.x() + size.x() >= textureSize.x() ||
|
|
|
|
writePos.y() + size.y() >= textureSize.y()) {
|
|
|
|
// Nope, still won't fit.
|
|
|
|
return false;
|
|
|
|
}
|
2014-07-27 21:44:02 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
cursor_out = writePos;
|
|
|
|
writePos[0] += size.x() + 1; // Leave 1px of space between glyphs.
|
2013-10-04 23:24:41 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
if (size.y() > rowHeight)
|
|
|
|
rowHeight = size.y();
|
2014-07-27 21:44:02 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
return true;
|
2014-07-27 21:44:02 +00:00
|
|
|
}
|
2013-10-04 23:24:41 +00:00
|
|
|
|
2014-08-11 23:05:18 +00:00
|
|
|
void Font::FontTexture::initTexture()
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
assert(textureId == 0);
|
|
|
|
textureId = Renderer::createTexture(Renderer::Texture::ALPHA, false, false,
|
|
|
|
textureSize.x(), textureSize.y(), nullptr);
|
2014-08-11 23:05:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Font::FontTexture::deinitTexture()
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
if (textureId != 0)
|
|
|
|
{
|
|
|
|
Renderer::destroyTexture(textureId);
|
|
|
|
textureId = 0;
|
|
|
|
}
|
2014-08-11 23:05:18 +00:00
|
|
|
}
|
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
void Font::getTextureForNewGlyph(const Vector2i& glyphSize,
|
|
|
|
FontTexture*& tex_out, Vector2i& cursor_out)
|
2014-07-27 21:44:02 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
if (mTextures.size()) {
|
|
|
|
// Check if the most recent texture has space.
|
|
|
|
tex_out = &mTextures.back();
|
2014-07-27 21:44:02 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// Will this one work?
|
|
|
|
if (tex_out->findEmpty(glyphSize, cursor_out))
|
|
|
|
return; // Yes.
|
|
|
|
}
|
2014-07-27 21:44:02 +00:00
|
|
|
|
2021-01-16 17:05:48 +00:00
|
|
|
// Current textures are full, make a new one.
|
|
|
|
mTextures.push_back(FontTexture(mSize));
|
2020-06-21 12:25:28 +00:00
|
|
|
tex_out = &mTextures.back();
|
|
|
|
tex_out->initTexture();
|
2019-08-25 15:23:02 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
bool ok = tex_out->findEmpty(glyphSize, cursor_out);
|
|
|
|
if (!ok) {
|
|
|
|
LOG(LogError) << "Glyph too big to fit on a new texture (glyph size > " <<
|
2021-01-13 18:48:31 +00:00
|
|
|
tex_out->textureSize.x() << ", " << tex_out->textureSize.y() << ")";
|
2020-06-21 12:25:28 +00:00
|
|
|
tex_out = nullptr;
|
|
|
|
}
|
2014-07-27 21:44:02 +00:00
|
|
|
}
|
2013-10-04 23:24:41 +00:00
|
|
|
|
2014-08-30 20:36:56 +00:00
|
|
|
std::vector<std::string> getFallbackFontPaths()
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
std::vector<std::string> fontPaths;
|
|
|
|
|
2020-08-15 07:28:47 +00:00
|
|
|
// Standard fonts, let's include them here for exception handling purposes even though that's
|
2020-07-08 15:01:47 +00:00
|
|
|
// not really the correct location. (The application will crash if they are missing.)
|
|
|
|
ResourceManager::getInstance()->
|
|
|
|
getResourcePath(":/fonts/opensans_hebrew_condensed_light.ttf");
|
|
|
|
ResourceManager::getInstance()->
|
|
|
|
getResourcePath(":/fonts/opensans_hebrew_condensed_regular.ttf");
|
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// Vera sans Unicode:
|
|
|
|
fontPaths.push_back(ResourceManager::getInstance()->
|
2020-06-21 17:35:43 +00:00
|
|
|
getResourcePath(":/fonts/DejaVuSans.ttf"));
|
2020-06-21 12:25:28 +00:00
|
|
|
// Freefont monospaced:
|
|
|
|
fontPaths.push_back(ResourceManager::getInstance()->
|
2020-06-21 17:35:43 +00:00
|
|
|
getResourcePath(":/fonts/FreeMono.ttf"));
|
2020-06-21 12:25:28 +00:00
|
|
|
// Various languages, such as Japanese and Chinese:
|
|
|
|
fontPaths.push_back(ResourceManager::getInstance()->
|
2020-06-21 17:35:43 +00:00
|
|
|
getResourcePath(":/fonts/DroidSansFallbackFull.ttf"));
|
2020-06-21 12:25:28 +00:00
|
|
|
// Korean:
|
|
|
|
fontPaths.push_back(ResourceManager::getInstance()->
|
2020-06-21 17:35:43 +00:00
|
|
|
getResourcePath(":/fonts/NanumMyeongjo.ttf"));
|
2020-06-21 12:25:28 +00:00
|
|
|
// Font Awesome icon glyphs, used for star-flagging favorites in the gamelists:
|
|
|
|
fontPaths.push_back(ResourceManager::getInstance()->
|
2020-06-21 17:35:43 +00:00
|
|
|
getResourcePath(":/fonts/fontawesome-webfont.ttf"));
|
2020-06-21 12:25:28 +00:00
|
|
|
|
|
|
|
fontPaths.shrink_to_fit();
|
|
|
|
return fontPaths;
|
2014-08-30 20:36:56 +00:00
|
|
|
}
|
|
|
|
|
2017-11-10 18:48:23 +00:00
|
|
|
FT_Face Font::getFaceForChar(unsigned int id)
|
2014-08-30 20:36:56 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
static const std::vector<std::string> fallbackFonts = getFallbackFontPaths();
|
2014-08-30 20:36:56 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// Look through our current font + fallback fonts to see if any have the
|
|
|
|
// glyph we're looking for.
|
|
|
|
for (unsigned int i = 0; i < fallbackFonts.size() + 1; i++) {
|
|
|
|
auto fit = mFaceCache.find(i);
|
2014-08-30 20:36:56 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// Doesn't exist yet.
|
|
|
|
if (fit == mFaceCache.cend()) {
|
|
|
|
// i == 0 -> mPath
|
|
|
|
// Otherwise, take from fallbackFonts.
|
|
|
|
const std::string& path = (i == 0 ? mPath : fallbackFonts.at(i - 1));
|
|
|
|
ResourceData data = ResourceManager::getInstance()->getFileData(path);
|
|
|
|
mFaceCache[i] = std::unique_ptr<FontFace>(new FontFace(std::move(data), mSize));
|
|
|
|
fit = mFaceCache.find(i);
|
|
|
|
}
|
2014-08-30 20:36:56 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
if (FT_Get_Char_Index(fit->second->face, id) != 0)
|
|
|
|
return fit->second->face;
|
|
|
|
}
|
2014-08-30 20:36:56 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// Nothing has a valid glyph - return the "real" face so we get a "missing" character.
|
|
|
|
return mFaceCache.cbegin()->second->face;
|
2014-08-30 20:36:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Font::clearFaceCache()
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
mFaceCache.clear();
|
2014-08-30 20:36:56 +00:00
|
|
|
}
|
|
|
|
|
2017-11-10 18:48:23 +00:00
|
|
|
Font::Glyph* Font::getGlyph(unsigned int id)
|
2014-07-27 21:44:02 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
// Is it already loaded?
|
|
|
|
auto it = mGlyphMap.find(id);
|
|
|
|
if (it != mGlyphMap.cend())
|
|
|
|
return &it->second;
|
2013-10-04 23:24:41 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// Nope, need to make a glyph.
|
|
|
|
FT_Face face = getFaceForChar(id);
|
|
|
|
if (!face) {
|
2021-01-13 18:48:31 +00:00
|
|
|
LOG(LogError) << "Couldn't find appropriate font face for character " <<
|
2020-06-21 12:25:28 +00:00
|
|
|
id << " for font " << mPath;
|
|
|
|
return nullptr;
|
|
|
|
}
|
2013-10-04 23:24:41 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
FT_GlyphSlot g = face->glyph;
|
2013-10-04 23:24:41 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
if (FT_Load_Char(face, id, FT_LOAD_RENDER)) {
|
2021-01-13 18:48:31 +00:00
|
|
|
LOG(LogError) << "Couldn't find glyph for character " <<
|
|
|
|
id << " for font " << mPath << ", size " << mSize;
|
2020-06-21 12:25:28 +00:00
|
|
|
return nullptr;
|
|
|
|
}
|
2014-07-27 21:44:02 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
Vector2i glyphSize(g->bitmap.width, g->bitmap.rows);
|
2014-07-27 21:44:02 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
FontTexture* tex = nullptr;
|
|
|
|
Vector2i cursor;
|
|
|
|
getTextureForNewGlyph(glyphSize, tex, cursor);
|
2013-10-04 23:24:41 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// getTextureForNewGlyph can fail if the glyph is bigger than the max texture
|
|
|
|
// size (absurdly large font size).
|
|
|
|
if (tex == nullptr) {
|
2021-01-13 18:48:31 +00:00
|
|
|
LOG(LogError) << "Couldn't create glyph for character " << id << " for font " <<
|
|
|
|
mPath << ", size " << mSize << " (no suitable texture found)";
|
2020-06-21 12:25:28 +00:00
|
|
|
return nullptr;
|
|
|
|
}
|
2013-10-04 23:24:41 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// Create glyph.
|
|
|
|
Glyph& glyph = mGlyphMap[id];
|
2019-08-25 15:23:02 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
glyph.texture = tex;
|
2020-11-17 22:06:54 +00:00
|
|
|
glyph.texPos = Vector2f(cursor.x() / static_cast<float>(tex->textureSize.x()),
|
|
|
|
cursor.y() / static_cast<float>(tex->textureSize.y()));
|
|
|
|
glyph.texSize = Vector2f(glyphSize.x() / static_cast<float>(tex->textureSize.x()),
|
|
|
|
glyphSize.y() / static_cast<float>(tex->textureSize.y()));
|
2014-07-27 21:44:02 +00:00
|
|
|
|
2020-11-17 22:06:54 +00:00
|
|
|
glyph.advance = Vector2f(static_cast<float>(g->metrics.horiAdvance) / 64.0f,
|
|
|
|
static_cast<float>(g->metrics.vertAdvance) / 64.0f);
|
|
|
|
glyph.bearing = Vector2f(static_cast<float>(g->metrics.horiBearingX) / 64.0f,
|
|
|
|
static_cast<float>(g->metrics.horiBearingY) / 64.0f);
|
2014-07-27 21:44:02 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// Upload glyph bitmap to texture.
|
|
|
|
Renderer::updateTexture(tex->textureId, Renderer::Texture::ALPHA, cursor.x(),
|
|
|
|
cursor.y(), glyphSize.x(), glyphSize.y(), g->bitmap.buffer);
|
2013-10-04 23:24:41 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// Update max glyph height.
|
|
|
|
if (glyphSize.y() > mMaxGlyphHeight)
|
|
|
|
mMaxGlyphHeight = glyphSize.y();
|
2014-07-27 21:44:02 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// Done.
|
|
|
|
return &glyph;
|
2013-10-04 23:24:41 +00:00
|
|
|
}
|
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// Completely recreate the texture data for all textures based on mGlyphs information.
|
2014-08-11 23:05:18 +00:00
|
|
|
void Font::rebuildTextures()
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
// Recreate OpenGL textures.
|
|
|
|
for (auto it = mTextures.begin(); it != mTextures.end(); it++)
|
|
|
|
it->initTexture();
|
2014-08-11 23:05:18 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// Reupload the texture data.
|
|
|
|
for (auto it = mGlyphMap.cbegin(); it != mGlyphMap.cend(); it++) {
|
|
|
|
FT_Face face = getFaceForChar(it->first);
|
|
|
|
FT_GlyphSlot glyphSlot = face->glyph;
|
2014-08-30 20:36:56 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// Load the glyph bitmap through FT.
|
|
|
|
FT_Load_Char(face, it->first, FT_LOAD_RENDER);
|
2014-08-11 23:05:18 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
FontTexture* tex = it->second.texture;
|
2019-08-25 15:23:02 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// Find the position/size.
|
2020-11-17 22:06:54 +00:00
|
|
|
Vector2i cursor(static_cast<int>(it->second.texPos.x() * tex->textureSize.x()),
|
|
|
|
static_cast<int>(it->second.texPos.y() * tex->textureSize.y()));
|
|
|
|
Vector2i glyphSize(static_cast<int>(it->second.texSize.x() * tex->textureSize.x()),
|
|
|
|
static_cast<int>(it->second.texSize.y() * tex->textureSize.y()));
|
2019-08-25 15:23:02 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// Upload to texture.
|
|
|
|
Renderer::updateTexture(tex->textureId, Renderer::Texture::ALPHA,
|
|
|
|
cursor.x(), cursor.y(), glyphSize.x(), glyphSize.y(), glyphSlot->bitmap.buffer);
|
|
|
|
}
|
2014-08-11 23:05:18 +00:00
|
|
|
}
|
|
|
|
|
2013-10-04 23:24:41 +00:00
|
|
|
void Font::renderTextCache(TextCache* cache)
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
if (cache == nullptr) {
|
2021-01-13 18:48:31 +00:00
|
|
|
LOG(LogError) << "Attempted to draw nullptr TextCache";
|
2020-06-21 12:25:28 +00:00
|
|
|
return;
|
|
|
|
}
|
2013-10-04 23:24:41 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
for (auto it = cache->vertexLists.cbegin(); it != cache->vertexLists.cend(); it++) {
|
|
|
|
assert(*it->textureIdPtr != 0);
|
2014-07-27 21:44:02 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
auto vertexList = *it;
|
2014-07-27 21:44:02 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
Renderer::bindTexture(*it->textureIdPtr);
|
2020-12-29 11:54:24 +00:00
|
|
|
Renderer::drawTriangleStrips(&it->verts[0],
|
|
|
|
static_cast<const unsigned int>(it->verts.size()));
|
2020-06-21 12:25:28 +00:00
|
|
|
}
|
2013-10-04 23:24:41 +00:00
|
|
|
}
|
|
|
|
|
2017-10-28 20:24:35 +00:00
|
|
|
Vector2f Font::sizeText(std::string text, float lineSpacing)
|
2013-10-04 23:24:41 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
float lineWidth = 0.0f;
|
|
|
|
float highestWidth = 0.0f;
|
2013-10-04 23:24:41 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
const float lineHeight = getHeight(lineSpacing);
|
2014-07-27 21:44:02 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
float y = lineHeight;
|
2013-10-04 23:24:41 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
size_t i = 0;
|
|
|
|
while (i < text.length()) {
|
|
|
|
unsigned int character = Utils::String::chars2Unicode(text, i); // Advances i.
|
2013-10-04 23:24:41 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
if (character == '\n') {
|
|
|
|
if (lineWidth > highestWidth)
|
|
|
|
highestWidth = lineWidth;
|
2013-10-04 23:24:41 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
lineWidth = 0.0f;
|
|
|
|
y += lineHeight;
|
|
|
|
}
|
2013-10-04 23:24:41 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
Glyph* glyph = getGlyph(character);
|
|
|
|
if (glyph)
|
|
|
|
lineWidth += glyph->advance.x();
|
|
|
|
}
|
2013-10-04 23:24:41 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
if (lineWidth > highestWidth)
|
|
|
|
highestWidth = lineWidth;
|
2013-10-04 23:24:41 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
return Vector2f(highestWidth, y);
|
2013-10-04 23:24:41 +00:00
|
|
|
}
|
|
|
|
|
2014-05-12 22:05:28 +00:00
|
|
|
float Font::getHeight(float lineSpacing) const
|
2013-10-04 23:24:41 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
return mMaxGlyphHeight * lineSpacing;
|
2013-10-04 23:24:41 +00:00
|
|
|
}
|
|
|
|
|
2014-07-27 21:44:02 +00:00
|
|
|
float Font::getLetterHeight()
|
2014-03-22 21:02:25 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
Glyph* glyph = getGlyph('S');
|
|
|
|
assert(glyph);
|
|
|
|
return glyph->texSize.y() * glyph->texture->textureSize.y();
|
2014-03-22 21:02:25 +00:00
|
|
|
}
|
2013-10-04 23:24:41 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// Breaks up a normal string with newlines to make it fit xLen.
|
2014-07-27 21:44:02 +00:00
|
|
|
std::string Font::wrapText(std::string text, float xLen)
|
2013-10-04 23:24:41 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
std::string out;
|
|
|
|
std::string line;
|
|
|
|
std::string word;
|
|
|
|
std::string temp;
|
2013-10-04 23:24:41 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
size_t space;
|
|
|
|
Vector2f textSize;
|
2013-10-04 23:24:41 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// While there's text or we still have text to render.
|
|
|
|
while (text.length() > 0) {
|
|
|
|
space = text.find_first_of(" \t\n");
|
|
|
|
if (space == std::string::npos)
|
|
|
|
space = text.length() - 1;
|
2013-10-04 23:24:41 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
word = text.substr(0, space + 1);
|
|
|
|
text.erase(0, space + 1);
|
2013-10-04 23:24:41 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
temp = line + word;
|
2013-10-04 23:24:41 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
textSize = sizeText(temp);
|
2013-10-04 23:24:41 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// If the word will fit on the line, add it to our line, and continue.
|
|
|
|
if (textSize.x() <= xLen) {
|
|
|
|
line = temp;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// The next word won't fit, so break here.
|
|
|
|
out += line + '\n';
|
|
|
|
line = word;
|
|
|
|
}
|
|
|
|
}
|
2013-10-04 23:24:41 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// Whatever's left should fit.
|
|
|
|
out += line;
|
2013-10-04 23:24:41 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
return out;
|
2013-10-04 23:24:41 +00:00
|
|
|
}
|
|
|
|
|
2017-10-28 20:24:35 +00:00
|
|
|
Vector2f Font::sizeWrappedText(std::string text, float xLen, float lineSpacing)
|
2013-10-04 23:24:41 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
text = wrapText(text, xLen);
|
|
|
|
return sizeText(text, lineSpacing);
|
|
|
|
}
|
|
|
|
|
|
|
|
Vector2f Font::getWrappedTextCursorOffset(std::string text, float xLen,
|
|
|
|
size_t stop, float lineSpacing)
|
|
|
|
{
|
|
|
|
std::string wrappedText = wrapText(text, xLen);
|
|
|
|
|
|
|
|
float lineWidth = 0.0f;
|
|
|
|
float y = 0.0f;
|
|
|
|
|
|
|
|
size_t wrapCursor = 0;
|
|
|
|
size_t cursor = 0;
|
|
|
|
while (cursor < stop) {
|
|
|
|
unsigned int wrappedCharacter = Utils::String::chars2Unicode(wrappedText, wrapCursor);
|
|
|
|
unsigned int character = Utils::String::chars2Unicode(text, cursor);
|
|
|
|
|
|
|
|
if (wrappedCharacter == '\n' && character != '\n') {
|
|
|
|
// This is where the wordwrap inserted a newline
|
|
|
|
// Reset lineWidth and increment y, but don't consume .a cursor character.
|
|
|
|
lineWidth = 0.0f;
|
|
|
|
y += getHeight(lineSpacing);
|
|
|
|
|
|
|
|
cursor = Utils::String::prevCursor(text, cursor); // Unconsume.
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (character == '\n') {
|
|
|
|
lineWidth = 0.0f;
|
|
|
|
y += getHeight(lineSpacing);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
Glyph* glyph = getGlyph(character);
|
|
|
|
if (glyph)
|
|
|
|
lineWidth += glyph->advance.x();
|
|
|
|
}
|
|
|
|
|
|
|
|
return Vector2f(lineWidth, y);
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// TextCache.
|
|
|
|
//
|
|
|
|
|
|
|
|
float Font::getNewlineStartOffset(const std::string& text, const unsigned int& charStart,
|
|
|
|
const float& xLen, const Alignment& alignment)
|
|
|
|
{
|
|
|
|
switch (alignment) {
|
|
|
|
case ALIGN_LEFT:
|
|
|
|
return 0;
|
|
|
|
case ALIGN_CENTER: {
|
2020-06-25 17:52:38 +00:00
|
|
|
int endChar = 0;
|
2020-11-17 22:06:54 +00:00
|
|
|
endChar = static_cast<int>(text.find('\n', charStart));
|
2020-06-21 12:25:28 +00:00
|
|
|
return (xLen - sizeText(text.substr(charStart, endChar !=
|
|
|
|
std::string::npos ? endChar - charStart : endChar)).x()) / 2.0f;
|
|
|
|
}
|
|
|
|
case ALIGN_RIGHT: {
|
2020-11-17 22:06:54 +00:00
|
|
|
int endChar = static_cast<int>(text.find('\n', charStart));
|
2020-06-21 12:25:28 +00:00
|
|
|
return xLen - (sizeText(text.substr(charStart, endChar !=
|
|
|
|
std::string::npos ? endChar - charStart : endChar)).x());
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TextCache* Font::buildTextCache(
|
|
|
|
const std::string& text,
|
|
|
|
Vector2f offset,
|
|
|
|
unsigned int color,
|
|
|
|
float xLen,
|
|
|
|
Alignment alignment,
|
|
|
|
float lineSpacing)
|
|
|
|
{
|
|
|
|
float x = offset[0] + (xLen != 0 ? getNewlineStartOffset(text, 0, xLen, alignment) : 0);
|
|
|
|
|
|
|
|
float yTop = getGlyph('S')->bearing.y();
|
|
|
|
float yBot = getHeight(lineSpacing);
|
|
|
|
float y = offset[1] + (yBot + yTop)/2.0f;
|
|
|
|
|
|
|
|
// Vertices by texture.
|
2020-07-15 15:44:27 +00:00
|
|
|
std::map<FontTexture*, std::vector<Renderer::Vertex>> vertMap;
|
2020-06-21 12:25:28 +00:00
|
|
|
|
|
|
|
size_t cursor = 0;
|
|
|
|
while (cursor < text.length()) {
|
|
|
|
// Also advances cursor.
|
|
|
|
unsigned int character = Utils::String::chars2Unicode(text, cursor);
|
|
|
|
Glyph* glyph;
|
|
|
|
|
|
|
|
// Invalid character.
|
|
|
|
if (character == 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (character == '\n') {
|
|
|
|
y += getHeight(lineSpacing);
|
|
|
|
x = offset[0] + (xLen != 0 ? getNewlineStartOffset(text,
|
2020-12-16 22:59:00 +00:00
|
|
|
static_cast<const unsigned int>(cursor) /* cursor is already advanced */,
|
2020-06-21 12:25:28 +00:00
|
|
|
xLen, alignment) : 0);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
glyph = getGlyph(character);
|
|
|
|
if (glyph == nullptr)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
std::vector<Renderer::Vertex>& verts = vertMap[glyph->texture];
|
|
|
|
size_t oldVertSize = verts.size();
|
|
|
|
verts.resize(oldVertSize + 6);
|
|
|
|
Renderer::Vertex* vertices = verts.data() + oldVertSize;
|
|
|
|
|
2020-07-15 15:44:27 +00:00
|
|
|
const float glyphStartX = x + glyph->bearing.x();
|
|
|
|
const Vector2i& textureSize = glyph->texture->textureSize;
|
2020-12-18 15:49:11 +00:00
|
|
|
const unsigned int convertedColor = Renderer::convertRGBAToABGR(color);
|
2020-06-21 12:25:28 +00:00
|
|
|
|
|
|
|
vertices[1] = {
|
|
|
|
{ glyphStartX, y - glyph->bearing.y() },
|
|
|
|
{ glyph->texPos.x(), glyph->texPos.y() },
|
|
|
|
convertedColor };
|
|
|
|
vertices[2] = {
|
|
|
|
{ glyphStartX, y - glyph->bearing.y() + (glyph->texSize.y() * textureSize.y()) },
|
|
|
|
{ glyph->texPos.x(), glyph->texPos.y() + glyph->texSize.y() },
|
|
|
|
convertedColor };
|
|
|
|
vertices[3] = {
|
|
|
|
{ glyphStartX + glyph->texSize.x() * textureSize.x(), y - glyph->bearing.y() },
|
|
|
|
{ glyph->texPos.x() + glyph->texSize.x(), glyph->texPos.y() },
|
|
|
|
convertedColor };
|
|
|
|
vertices[4] = {
|
|
|
|
{ glyphStartX + glyph->texSize.x() * textureSize.x(), y - glyph->bearing.y() +
|
|
|
|
(glyph->texSize.y() * textureSize.y()) },
|
|
|
|
{ glyph->texPos.x() + glyph->texSize.x(), glyph->texPos.y() + glyph->texSize.y() },
|
|
|
|
convertedColor };
|
|
|
|
|
|
|
|
// Round vertices.
|
2021-01-16 17:05:48 +00:00
|
|
|
for (int i = 1; i < 5; i++)
|
2020-06-21 12:25:28 +00:00
|
|
|
vertices[i].pos.round();
|
|
|
|
|
|
|
|
// Make duplicates of first and last vertex so this can be rendered as a triangle strip.
|
|
|
|
vertices[0] = vertices[1];
|
|
|
|
vertices[5] = vertices[4];
|
|
|
|
|
|
|
|
// Advance.
|
|
|
|
x += glyph->advance.x();
|
|
|
|
}
|
|
|
|
|
|
|
|
//TextCache::CacheMetrics metrics = { sizeText(text, lineSpacing) };
|
|
|
|
|
|
|
|
TextCache* cache = new TextCache();
|
|
|
|
cache->vertexLists.resize(vertMap.size());
|
|
|
|
cache->metrics = { sizeText(text, lineSpacing) };
|
|
|
|
|
|
|
|
unsigned int i = 0;
|
|
|
|
for (auto it = vertMap.cbegin(); it != vertMap.cend(); it++) {
|
|
|
|
TextCache::VertexList& vertList = cache->vertexLists.at(i);
|
|
|
|
|
|
|
|
vertList.textureIdPtr = &it->first->textureId;
|
|
|
|
vertList.verts = it->second;
|
|
|
|
}
|
|
|
|
|
|
|
|
clearFaceCache();
|
|
|
|
|
|
|
|
return cache;
|
|
|
|
}
|
2014-07-27 21:44:02 +00:00
|
|
|
|
2020-11-16 16:46:36 +00:00
|
|
|
TextCache* Font::buildTextCache(
|
|
|
|
const std::string& text,
|
|
|
|
float offsetX,
|
|
|
|
float offsetY,
|
|
|
|
unsigned int color,
|
|
|
|
float lineSpacing)
|
2020-06-21 12:25:28 +00:00
|
|
|
{
|
2020-11-16 16:46:36 +00:00
|
|
|
return buildTextCache(text, Vector2f(offsetX, offsetY), color, 0.0f, ALIGN_LEFT, lineSpacing);
|
2014-05-13 01:03:02 +00:00
|
|
|
}
|
|
|
|
|
2013-10-04 23:24:41 +00:00
|
|
|
void TextCache::setColor(unsigned int color)
|
|
|
|
{
|
2020-12-18 15:49:11 +00:00
|
|
|
const unsigned int convertedColor = Renderer::convertRGBAToABGR(color);
|
2019-08-08 20:16:11 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
for (auto it = vertexLists.begin(); it != vertexLists.end(); it++)
|
|
|
|
for (auto it2 = it->verts.begin(); it2 != it->verts.end(); it2++)
|
|
|
|
it2->col = convertedColor;
|
2013-10-04 23:24:41 +00:00
|
|
|
}
|
2014-01-01 05:39:22 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
std::shared_ptr<Font> Font::getFromTheme(const ThemeData::ThemeElement* elem,
|
|
|
|
unsigned int properties, const std::shared_ptr<Font>& orig)
|
2014-01-01 05:39:22 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
using namespace ThemeFlags;
|
|
|
|
if (!(properties & FONT_PATH) && !(properties & FONT_SIZE))
|
|
|
|
return orig;
|
2019-08-25 15:23:02 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
std::shared_ptr<Font> font;
|
|
|
|
int size = (orig ? orig->mSize : FONT_SIZE_MEDIUM);
|
|
|
|
std::string path = (orig ? orig->mPath : getDefaultPath());
|
2014-01-01 05:39:22 +00:00
|
|
|
|
2020-11-17 22:06:54 +00:00
|
|
|
float sh = static_cast<float>(Renderer::getScreenHeight());
|
2020-06-21 12:25:28 +00:00
|
|
|
if (properties & FONT_SIZE && elem->has("fontSize"))
|
2020-11-17 22:06:54 +00:00
|
|
|
size = static_cast<int>(sh * elem->get<float>("fontSize"));
|
2020-06-21 12:25:28 +00:00
|
|
|
if (properties & FONT_PATH && elem->has("fontPath"))
|
|
|
|
path = elem->get<std::string>("fontPath");
|
2014-01-01 05:39:22 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
return get(size, path);
|
2014-01-01 05:39:22 +00:00
|
|
|
}
|