Fixed a font loading crash caused by an insufficient font texture size.

Also added a preventive measure for similar crashes in the future and made some general cleanup of the font code.
This commit is contained in:
Leon Styhre 2022-04-02 11:59:52 +02:00
parent 960a23ddc3
commit c4cae406e9
2 changed files with 69 additions and 59 deletions

View file

@ -59,7 +59,7 @@ size_t Font::getMemUsage() const
size_t Font::getTotalMemUsage() size_t Font::getTotalMemUsage()
{ {
size_t total = 0; size_t total {0};
auto it = sFontMap.cbegin(); auto it = sFontMap.cbegin();
while (it != sFontMap.cend()) { while (it != sFontMap.cend()) {
@ -118,8 +118,9 @@ Font::~Font()
std::shared_ptr<Font> Font::get(int size, const std::string& path) std::shared_ptr<Font> Font::get(int size, const std::string& path)
{ {
const std::string canonicalPath = Utils::FileSystem::getCanonicalPath(path); const std::string canonicalPath {Utils::FileSystem::getCanonicalPath(path)};
std::pair<std::string, int> def(canonicalPath.empty() ? getDefaultPath() : canonicalPath, size); std::pair<std::string, int> def {canonicalPath.empty() ? getDefaultPath() : canonicalPath,
size};
auto foundFont = sFontMap.find(def); auto foundFont = sFontMap.find(def);
if (foundFont != sFontMap.cend()) { if (foundFont != sFontMap.cend()) {
@ -127,7 +128,7 @@ std::shared_ptr<Font> Font::get(int size, const std::string& path)
return foundFont->second.lock(); return foundFont->second.lock();
} }
std::shared_ptr<Font> font = std::shared_ptr<Font>(new Font(def.second, def.first)); std::shared_ptr<Font> font {std::shared_ptr<Font>(new Font(def.second, def.first))};
sFontMap[def] = std::weak_ptr<Font>(font); sFontMap[def] = std::weak_ptr<Font>(font);
ResourceManager::getInstance().addReloadable(font); ResourceManager::getInstance().addReloadable(font);
return font; return font;
@ -145,21 +146,21 @@ Font::FontTexture::FontTexture(const int mSize)
// This is a hack to add some extra texture size when running at very low resolutions. If not // This is a hack to add some extra texture size when running at very low resolutions. If not
// doing this, the use of fallback fonts (such as Japanese characters) could result in the // doing this, the use of fallback fonts (such as Japanese characters) could result in the
// texture not fitting the glyphs which would crash the application. // texture not fitting the glyphs.
int extraTextureSize {0}; int extraTextureSize {0};
const float screenSizeModifier = const float screenSizeModifier {
std::min(Renderer::getScreenWidthModifier(), Renderer::getScreenHeightModifier()); std::min(Renderer::getScreenWidthModifier(), Renderer::getScreenHeightModifier())};
if (screenSizeModifier < 0.2f) if (screenSizeModifier < 0.2f)
extraTextureSize += 6; extraTextureSize += 6;
if (screenSizeModifier < 0.45f) if (screenSizeModifier < 0.45f)
extraTextureSize += 4; extraTextureSize += 4;
// It's not entirely clear if the 18 and 6 constants are correct, but they seem to provide // It's not entirely clear if the 20 and 8 constants are correct, but they seem to provide
// a texture buffer large enough to hold the fonts (otherwise the application would crash). // a texture buffer large enough to hold the fonts. This logic is obviously a hack though
// This logic is obviously a hack though and needs to be properly reviewed and improved. // and needs to be properly reviewed and improved.
textureSize = glm::ivec2 {mSize * (18 + extraTextureSize), mSize * (6 + extraTextureSize / 2)}; textureSize = glm::ivec2 {mSize * (20 + extraTextureSize), mSize * (8 + extraTextureSize / 2)};
writePos = glm::ivec2 {}; writePos = glm::ivec2 {0, 0};
rowHeight = 0; rowHeight = 0;
} }
@ -225,7 +226,16 @@ void Font::getTextureForNewGlyph(const glm::ivec2& glyphSize,
return; // Yes. return; // Yes.
} }
// Current textures are full, make a new one. // This should never happen, assuming the texture size is large enough to fit the font,
// as set in the FontTexture constructor. In the unlikely situation that it still happens,
// setting the texture to nullptr makes sure the application doesn't crash and that the
// user is clearly notified of the problem by the fact that the glyph/character will be
// completely missing.
if (mGlyphMap.size() > 0) {
tex_out = nullptr;
return;
}
mTextures.push_back(FontTexture(mSize)); mTextures.push_back(FontTexture(mSize));
tex_out = &mTextures.back(); tex_out = &mTextures.back();
tex_out->initTexture(); tex_out->initTexture();
@ -270,7 +280,7 @@ std::vector<std::string> getFallbackFontPaths()
FT_Face Font::getFaceForChar(unsigned int id) FT_Face Font::getFaceForChar(unsigned int id)
{ {
static const std::vector<std::string> fallbackFonts = getFallbackFontPaths(); static const std::vector<std::string> fallbackFonts {getFallbackFontPaths()};
// Look through our current font + fallback fonts to see if any have the // Look through our current font + fallback fonts to see if any have the
// glyph we're looking for. // glyph we're looking for.
@ -295,7 +305,7 @@ FT_Face Font::getFaceForChar(unsigned int id)
return mFaceCache.cbegin()->second->face; return mFaceCache.cbegin()->second->face;
} }
Font::Glyph* Font::getGlyph(unsigned int id) Font::Glyph* Font::getGlyph(const unsigned int id)
{ {
// Is it already loaded? // Is it already loaded?
auto it = mGlyphMap.find(id); auto it = mGlyphMap.find(id);
@ -303,14 +313,14 @@ Font::Glyph* Font::getGlyph(unsigned int id)
return &it->second; return &it->second;
// Nope, need to make a glyph. // Nope, need to make a glyph.
FT_Face face = getFaceForChar(id); FT_Face face {getFaceForChar(id)};
if (!face) { if (!face) {
LOG(LogError) << "Couldn't find appropriate font face for character " << id << " for font " LOG(LogError) << "Couldn't find appropriate font face for character " << id << " for font "
<< mPath; << mPath;
return nullptr; return nullptr;
} }
FT_GlyphSlot g = face->glyph; FT_GlyphSlot g {face->glyph};
if (FT_Load_Char(face, id, FT_LOAD_RENDER)) { if (FT_Load_Char(face, id, FT_LOAD_RENDER)) {
LOG(LogError) << "Couldn't find glyph for character " << id << " for font " << mPath LOG(LogError) << "Couldn't find glyph for character " << id << " for font " << mPath
@ -320,8 +330,8 @@ Font::Glyph* Font::getGlyph(unsigned int id)
glm::ivec2 glyphSize {g->bitmap.width, g->bitmap.rows}; glm::ivec2 glyphSize {g->bitmap.width, g->bitmap.rows};
FontTexture* tex = nullptr; FontTexture* tex {nullptr};
glm::ivec2 cursor; glm::ivec2 cursor {0, 0};
getTextureForNewGlyph(glyphSize, tex, cursor); getTextureForNewGlyph(glyphSize, tex, cursor);
// getTextureForNewGlyph can fail if the glyph is bigger than the max texture // getTextureForNewGlyph can fail if the glyph is bigger than the max texture
@ -333,7 +343,7 @@ Font::Glyph* Font::getGlyph(unsigned int id)
} }
// Create glyph. // Create glyph.
Glyph& glyph = mGlyphMap[id]; Glyph& glyph {mGlyphMap[id]};
glyph.texture = tex; glyph.texture = tex;
glyph.texPos = glm::vec2 {cursor.x / static_cast<float>(tex->textureSize.x), glyph.texPos = glm::vec2 {cursor.x / static_cast<float>(tex->textureSize.x),
@ -366,13 +376,13 @@ void Font::rebuildTextures()
// Re-upload the texture data. // Re-upload the texture data.
for (auto it = mGlyphMap.cbegin(); it != mGlyphMap.cend(); ++it) { for (auto it = mGlyphMap.cbegin(); it != mGlyphMap.cend(); ++it) {
FT_Face face = getFaceForChar(it->first); FT_Face face {getFaceForChar(it->first)};
FT_GlyphSlot glyphSlot = face->glyph; FT_GlyphSlot glyphSlot {face->glyph};
// Load the glyph bitmap through FT. // Load the glyph bitmap through FT.
FT_Load_Char(face, it->first, FT_LOAD_RENDER); FT_Load_Char(face, it->first, FT_LOAD_RENDER);
FontTexture* tex = it->second.texture; FontTexture* tex {it->second.texture};
// Find the position/size. // Find the position/size.
glm::ivec2 cursor {static_cast<int>(it->second.texPos.x * tex->textureSize.x), glm::ivec2 cursor {static_cast<int>(it->second.texPos.x * tex->textureSize.x),
@ -407,16 +417,16 @@ void Font::renderTextCache(TextCache* cache)
glm::vec2 Font::sizeText(std::string text, float lineSpacing) glm::vec2 Font::sizeText(std::string text, float lineSpacing)
{ {
float lineWidth = 0.0f; float lineWidth {0.0f};
float highestWidth = 0.0f; float highestWidth {0.0f};
const float lineHeight = getHeight(lineSpacing); const float lineHeight {getHeight(lineSpacing)};
float y = lineHeight; float y {lineHeight};
size_t i = 0; size_t i {0};
while (i < text.length()) { while (i < text.length()) {
unsigned int character = Utils::String::chars2Unicode(text, i); // Advances i. unsigned int character {Utils::String::chars2Unicode(text, i)}; // Advances i.
if (character == '\n') { if (character == '\n') {
if (lineWidth > highestWidth) if (lineWidth > highestWidth)
@ -426,7 +436,7 @@ glm::vec2 Font::sizeText(std::string text, float lineSpacing)
y += lineHeight; y += lineHeight;
} }
Glyph* glyph = getGlyph(character); Glyph* glyph {getGlyph(character)};
if (glyph) if (glyph)
lineWidth += glyph->advance.x; lineWidth += glyph->advance.x;
} }
@ -439,7 +449,7 @@ glm::vec2 Font::sizeText(std::string text, float lineSpacing)
std::string Font::getTextMaxWidth(std::string text, float maxWidth) std::string Font::getTextMaxWidth(std::string text, float maxWidth)
{ {
float width = sizeText(text).x; float width {sizeText(text).x};
while (width > maxWidth) { while (width > maxWidth) {
text.pop_back(); text.pop_back();
width = sizeText(text).x; width = sizeText(text).x;
@ -455,7 +465,7 @@ float Font::getHeight(float lineSpacing) const
float Font::getLetterHeight() float Font::getLetterHeight()
{ {
Glyph* glyph = getGlyph('S'); Glyph* glyph {getGlyph('S')};
assert(glyph); assert(glyph);
return glyph->texSize.y * glyph->texture->textureSize.y; return glyph->texSize.y * glyph->texture->textureSize.y;
} }
@ -468,9 +478,9 @@ std::string Font::wrapText(std::string text, float xLen)
std::string abbreviatedWord; std::string abbreviatedWord;
std::string temp; std::string temp;
size_t space; size_t space {0};
glm::vec2 textSize; glm::vec2 textSize {0.0f, 0.0f};
float dotsSize = sizeText("...").x; float dotsSize {sizeText("...").x};
// While there's text or we still have text to render. // While there's text or we still have text to render.
while (text.length() > 0) { while (text.length() > 0) {
@ -495,7 +505,7 @@ std::string Font::wrapText(std::string text, float xLen)
// If the word is too long to fit within xLen, then abbreviate it. // If the word is too long to fit within xLen, then abbreviate it.
if (xLen > 0 && sizeText(word).x > xLen) { if (xLen > 0 && sizeText(word).x > xLen) {
float length = xLen - dotsSize; float length {xLen - dotsSize};
if (length < 0) if (length < 0)
length = 0; length = 0;
abbreviatedWord = getTextMaxWidth(word, length); abbreviatedWord = getTextMaxWidth(word, length);
@ -527,16 +537,16 @@ glm::vec2 Font::getWrappedTextCursorOffset(std::string text,
size_t stop, size_t stop,
float lineSpacing) float lineSpacing)
{ {
std::string wrappedText = wrapText(text, xLen); std::string wrappedText {wrapText(text, xLen)};
float lineWidth = 0.0f; float lineWidth {0.0f};
float y = 0.0f; float y {0.0f};
size_t wrapCursor = 0; size_t wrapCursor {0};
size_t cursor = 0; size_t cursor {0};
while (cursor < stop) { while (cursor < stop) {
unsigned int wrappedCharacter = Utils::String::chars2Unicode(wrappedText, wrapCursor); unsigned int wrappedCharacter {Utils::String::chars2Unicode(wrappedText, wrapCursor)};
unsigned int character = Utils::String::chars2Unicode(text, cursor); unsigned int character {Utils::String::chars2Unicode(text, cursor)};
if (wrappedCharacter == '\n' && character != '\n') { if (wrappedCharacter == '\n' && character != '\n') {
// This is where the wordwrap inserted a newline // This is where the wordwrap inserted a newline
@ -576,7 +586,7 @@ float Font::getNewlineStartOffset(const std::string& text,
return 0; return 0;
} }
case ALIGN_CENTER: { case ALIGN_CENTER: {
int endChar = 0; int endChar {0};
endChar = static_cast<int>(text.find('\n', charStart)); endChar = static_cast<int>(text.find('\n', charStart));
return (xLen - sizeText(text.substr(charStart, return (xLen - sizeText(text.substr(charStart,
static_cast<size_t>(endChar) != std::string::npos ? static_cast<size_t>(endChar) != std::string::npos ?
@ -606,9 +616,9 @@ TextCache* Font::buildTextCache(const std::string& text,
float lineSpacing, float lineSpacing,
bool noTopMargin) bool noTopMargin)
{ {
float x = offset[0] + (xLen != 0 ? getNewlineStartOffset(text, 0, xLen, alignment) : 0); float x {offset[0] + (xLen != 0 ? getNewlineStartOffset(text, 0, xLen, alignment) : 0)};
float yTop = 0; float yTop {0.0f};
float yBot = 0; float yBot {0.0f};
if (noTopMargin) { if (noTopMargin) {
yTop = 0; yTop = 0;
@ -619,16 +629,16 @@ TextCache* Font::buildTextCache(const std::string& text,
yBot = getHeight(lineSpacing); yBot = getHeight(lineSpacing);
} }
float y = offset[1] + (yBot + yTop) / 2.0f; float y {offset[1] + (yBot + yTop) / 2.0f};
// Vertices by texture. // Vertices by texture.
std::map<FontTexture*, std::vector<Renderer::Vertex>> vertMap; std::map<FontTexture*, std::vector<Renderer::Vertex>> vertMap;
size_t cursor = 0; size_t cursor {0};
while (cursor < text.length()) { while (cursor < text.length()) {
// Also advances cursor. // Also advances cursor.
unsigned int character = Utils::String::chars2Unicode(text, cursor); unsigned int character {Utils::String::chars2Unicode(text, cursor)};
Glyph* glyph; Glyph* glyph {nullptr};
// Invalid character. // Invalid character.
if (character == 0) if (character == 0)
@ -649,10 +659,10 @@ TextCache* Font::buildTextCache(const std::string& text,
if (glyph == nullptr) if (glyph == nullptr)
continue; continue;
std::vector<Renderer::Vertex>& verts = vertMap[glyph->texture]; std::vector<Renderer::Vertex>& verts {vertMap[glyph->texture]};
size_t oldVertSize = verts.size(); size_t oldVertSize {verts.size()};
verts.resize(oldVertSize + 6); verts.resize(oldVertSize + 6);
Renderer::Vertex* vertices = verts.data() + oldVertSize; Renderer::Vertex* vertices {verts.data() + oldVertSize};
const float glyphStartX {x + glyph->bearing.x}; const float glyphStartX {x + glyph->bearing.x};
const glm::ivec2& textureSize {glyph->texture->textureSize}; const glm::ivec2& textureSize {glyph->texture->textureSize};
@ -682,11 +692,11 @@ TextCache* Font::buildTextCache(const std::string& text,
x += glyph->advance.x; x += glyph->advance.x;
} }
TextCache* cache = new TextCache(); TextCache* cache {new TextCache()};
cache->vertexLists.resize(vertMap.size()); cache->vertexLists.resize(vertMap.size());
cache->metrics = {sizeText(text, lineSpacing)}; cache->metrics = {sizeText(text, lineSpacing)};
unsigned int i = 0; unsigned int i {0};
for (auto it = vertMap.cbegin(); it != vertMap.cend(); ++it) { for (auto it = vertMap.cbegin(); it != vertMap.cend(); ++it) {
TextCache::VertexList& vertList = cache->vertexLists.at(i); TextCache::VertexList& vertList = cache->vertexLists.at(i);
@ -742,9 +752,9 @@ std::shared_ptr<Font> Font::getFromTheme(const ThemeData::ThemeElement* elem,
std::shared_ptr<Font> font; std::shared_ptr<Font> font;
int size = (orig ? orig->mSize : FONT_SIZE_MEDIUM); int size = (orig ? orig->mSize : FONT_SIZE_MEDIUM);
std::string path = (orig ? orig->mPath : getDefaultPath()); std::string path {orig ? orig->mPath : getDefaultPath()};
float sh = static_cast<float>(Renderer::getScreenHeight()); float sh {static_cast<float>(Renderer::getScreenHeight())};
if (properties & FONT_SIZE && elem->has("fontSize")) if (properties & FONT_SIZE && elem->has("fontSize"))
size = static_cast<int>(sh * elem->get<float>("fontSize")); size = static_cast<int>(sh * elem->get<float>("fontSize"));
if (properties & FONT_PATH && elem->has("fontPath")) if (properties & FONT_PATH && elem->has("fontPath"))

View file

@ -168,7 +168,7 @@ private:
std::map<unsigned int, Glyph> mGlyphMap; std::map<unsigned int, Glyph> mGlyphMap;
Glyph* getGlyph(unsigned int id); Glyph* getGlyph(const unsigned int id);
int mMaxGlyphHeight; int mMaxGlyphHeight;