Some font-related code and comments cleanup

This commit is contained in:
Leon Styhre 2024-08-04 13:43:44 +02:00
parent 03f6567dd5
commit a7d673f541
2 changed files with 20 additions and 51 deletions

View file

@ -3,8 +3,7 @@
// ES-DE Frontend // ES-DE Frontend
// Font.h // Font.h
// //
// Loading, unloading, caching, shaping and rendering of fonts. // Font management and text shaping and rendering.
// Also functions for text wrapping and similar.
// //
#include "resources/Font.h" #include "resources/Font.h"
@ -654,7 +653,7 @@ Font::FontTexture::FontTexture(const int mFontSize)
rowHeight = 0; rowHeight = 0;
writePos = glm::ivec2 {1, 1}; writePos = glm::ivec2 {1, 1};
// Set the texture atlas to a reasonable size, if we run out of space for adding glyphs then // Set the glyph atlas to a reasonable size, if we run out of space for adding glyphs then
// more textures will be created dynamically. // more textures will be created dynamically.
textureSize = glm::ivec2 {mFontSize * 6, mFontSize * 6}; textureSize = glm::ivec2 {mFontSize * 6, mFontSize * 6};
} }
@ -695,7 +694,7 @@ bool Font::FontTexture::findEmpty(const glm::ivec2& size, glm::ivec2& cursorOut)
void Font::FontTexture::initTexture() void Font::FontTexture::initTexture()
{ {
assert(textureId == 0); assert(textureId == 0);
// Create a black texture with zero alpha value so that the single-pixel spaces between the // Create a black texture with a zero alpha value so that single-pixel spaces between the
// glyphs will not be visible. That would otherwise lead to edge artifacts as these pixels // glyphs will not be visible. That would otherwise lead to edge artifacts as these pixels
// would get sampled during scaling. // would get sampled during scaling.
std::vector<uint8_t> texture(textureSize.x * textureSize.y * 4, 0); std::vector<uint8_t> texture(textureSize.x * textureSize.y * 4, 0);
@ -722,7 +721,7 @@ Font::FontFace::FontFace(ResourceData&& d, float size, const std::string& path,
// Even though a fractional font size can be requested, the glyphs will always be rounded // Even though a fractional font size can be requested, the glyphs will always be rounded
// to integers. It's not useless to call FT_Set_Char_Size() instead of FT_Set_Pixel_Sizes() // to integers. It's not useless to call FT_Set_Char_Size() instead of FT_Set_Pixel_Sizes()
// though as the glyphs will still be much more evenely sized across different resolutions. // though as the glyphs will still be much more evenly sized across different resolutions.
FT_Set_Char_Size(face, static_cast<FT_F26Dot6>(0.0f), static_cast<FT_F26Dot6>(size * 64.0f), 0, FT_Set_Char_Size(face, static_cast<FT_F26Dot6>(0.0f), static_cast<FT_F26Dot6>(size * 64.0f), 0,
0); 0);
fontHB = fontArg; fontHB = fontArg;
@ -867,6 +866,7 @@ void Font::shapeText(const std::string& text, std::vector<ShapeSegment>& segment
else { else {
// This also advances the cursor. // This also advances the cursor.
character = Utils::String::chars2Unicode(segment.substring, cursor); character = Utils::String::chars2Unicode(segment.substring, cursor);
Glyph* glyph {getGlyph(character)}; Glyph* glyph {getGlyph(character)};
segment.shapedWidth += glyph->advance.x; segment.shapedWidth += glyph->advance.x;
segment.glyphIndexes.emplace_back(std::make_pair(character, glyph->advance.x)); segment.glyphIndexes.emplace_back(std::make_pair(character, glyph->advance.x));
@ -877,7 +877,7 @@ void Font::shapeText(const std::string& text, std::vector<ShapeSegment>& segment
void Font::rebuildTextures() void Font::rebuildTextures()
{ {
// Recreate OpenGL textures. // Recreate all glyph atlas textures.
for (auto it = mTextures.begin(); it != mTextures.end(); ++it) for (auto it = mTextures.begin(); it != mTextures.end(); ++it)
(*it)->initTexture(); (*it)->initTexture();
@ -909,7 +909,6 @@ void Font::rebuildTextures()
getFaceForGlyphIndex(std::get<0>(it->first), std::get<1>(it->first), &returnedFont)}; getFaceForGlyphIndex(std::get<0>(it->first), std::get<1>(it->first), &returnedFont)};
FT_GlyphSlot glyphSlot {(*face)->glyph}; FT_GlyphSlot glyphSlot {(*face)->glyph};
// Load the glyph bitmap through FreeType.
FT_Load_Glyph(*face, std::get<0>(it->first), FT_LOAD_RENDER); FT_Load_Glyph(*face, std::get<0>(it->first), FT_LOAD_RENDER);
const glm::ivec2 glyphSize {glyphSlot->bitmap.width, glyphSlot->bitmap.rows}; const glm::ivec2 glyphSize {glyphSlot->bitmap.width, glyphSlot->bitmap.rows};
@ -917,7 +916,6 @@ void Font::rebuildTextures()
static_cast<int>(it->second.texPos.x * it->second.texture->textureSize.x), static_cast<int>(it->second.texPos.x * it->second.texture->textureSize.x),
static_cast<int>(it->second.texPos.y * it->second.texture->textureSize.y)}; static_cast<int>(it->second.texPos.y * it->second.texture->textureSize.y)};
// Upload glyph bitmap to texture.
if (glyphSize.x > 0 && glyphSize.y > 0) { if (glyphSize.x > 0 && glyphSize.y > 0) {
mRenderer->updateTexture(it->second.texture->textureId, 0, Renderer::TextureType::RED, mRenderer->updateTexture(it->second.texture->textureId, 0, Renderer::TextureType::RED,
cursor.x, cursor.y, glyphSize.x, glyphSize.y, cursor.x, cursor.y, glyphSize.x, glyphSize.y,
@ -995,7 +993,6 @@ FT_Face* Font::getFaceForGlyphIndex(unsigned int id, hb_font_t* fontArg, hb_font
} }
} }
// Couldn't find a valid glyph, return the current font face so we get a "no glyph" character.
*returnedFont = nullptr; *returnedFont = nullptr;
return &mFontFace->face; return &mFontFace->face;
} }
@ -1019,16 +1016,7 @@ Font::Glyph* Font::getGlyph(const unsigned int id)
const FT_GlyphSlot glyphSlot {(*face)->glyph}; const FT_GlyphSlot glyphSlot {(*face)->glyph};
// If the font does not contain hinting information then force the use of the automatic if (FT_Load_Char(*face, id, FT_LOAD_RENDER)) {
// hinter that is built into FreeType. Note: Using font-supplied hints generally looks worse
// than using the auto-hinter so it's disabled for now.
// const bool hasHinting {static_cast<bool>(glyphSlot->face->face_flags & FT_FACE_FLAG_HINTER)};
const bool hasHinting {true};
if (FT_Load_Char(*face, id,
(hasHinting ?
FT_LOAD_RENDER :
FT_LOAD_RENDER | FT_LOAD_FORCE_AUTOHINT | FT_LOAD_TARGET_LIGHT))) {
LOG(LogError) << "Couldn't find glyph for character " << id << " for font " << mPath LOG(LogError) << "Couldn't find glyph for character " << id << " for font " << mPath
<< ", size " << mFontSize; << ", size " << mFontSize;
return nullptr; return nullptr;
@ -1063,7 +1051,7 @@ Font::Glyph* Font::getGlyph(const unsigned int id)
glyph.bearing = {glyphSlot->metrics.horiBearingX >> 6, glyphSlot->metrics.horiBearingY >> 6}; glyph.bearing = {glyphSlot->metrics.horiBearingX >> 6, glyphSlot->metrics.horiBearingY >> 6};
glyph.rows = glyphSize.y; glyph.rows = glyphSize.y;
// Upload glyph bitmap to texture. // Upload glyph bitmap to glyph atlas texture.
if (glyphSize.x > 0 && glyphSize.y > 0) { if (glyphSize.x > 0 && glyphSize.y > 0) {
mRenderer->updateTexture(tex->textureId, 0, Renderer::TextureType::RED, cursor.x, cursor.y, mRenderer->updateTexture(tex->textureId, 0, Renderer::TextureType::RED, cursor.x, cursor.y,
glyphSize.x, glyphSize.y, glyphSlot->bitmap.buffer); glyphSize.x, glyphSize.y, glyphSlot->bitmap.buffer);
@ -1084,24 +1072,15 @@ Font::Glyph* Font::getGlyphByIndex(const unsigned int id, hb_font_t* fontArg, in
// We need to create a new entry. // We need to create a new entry.
FT_Face* face {getFaceForGlyphIndex(id, fontArg, &returnedFont)}; FT_Face* face {getFaceForGlyphIndex(id, fontArg, &returnedFont)};
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 glyph index " << id
<< mPath; << " for font " << mPath;
return nullptr; return nullptr;
} }
const FT_GlyphSlot glyphSlot {(*face)->glyph}; const FT_GlyphSlot glyphSlot {(*face)->glyph};
// If the font does not contain hinting information then force the use of the automatic if (FT_Load_Glyph(*face, id, FT_LOAD_RENDER)) {
// hinter that is built into FreeType. Note: Using font-supplied hints generally looks worse LOG(LogError) << "Couldn't find glyph for glyph index " << id << " for font " << mPath
// than using the auto-hinter so it's disabled for now.
// const bool hasHinting {static_cast<bool>(glyphSlot->face->face_flags & FT_FACE_FLAG_HINTER)};
const bool hasHinting {true};
if (FT_Load_Glyph(*face, id,
(hasHinting ?
FT_LOAD_RENDER :
FT_LOAD_RENDER | FT_LOAD_FORCE_AUTOHINT | FT_LOAD_TARGET_LIGHT))) {
LOG(LogError) << "Couldn't find glyph for character " << id << " for font " << mPath
<< ", size " << mFontSize; << ", size " << mFontSize;
return nullptr; return nullptr;
} }
@ -1126,7 +1105,7 @@ Font::Glyph* Font::getGlyphByIndex(const unsigned int id, hb_font_t* fontArg, in
// This should (hopefully) never occur as size constraints are enforced earlier on. // This should (hopefully) never occur as size constraints are enforced earlier on.
if (tex == nullptr) { if (tex == nullptr) {
LOG(LogError) << "Couldn't create glyph for character " << id << " for font " << mPath LOG(LogError) << "Couldn't create glyph for glyph index " << id << " for font " << mPath
<< ", size " << mFontSize << " (no suitable texture found)"; << ", size " << mFontSize << " (no suitable texture found)";
return nullptr; return nullptr;
} }
@ -1148,7 +1127,7 @@ Font::Glyph* Font::getGlyphByIndex(const unsigned int id, hb_font_t* fontArg, in
glyph.bearing = {glyphSlot->metrics.horiBearingX >> 6, glyphSlot->metrics.horiBearingY >> 6}; glyph.bearing = {glyphSlot->metrics.horiBearingX >> 6, glyphSlot->metrics.horiBearingY >> 6};
glyph.rows = glyphSize.y; glyph.rows = glyphSize.y;
// Upload glyph bitmap to texture. // Upload glyph bitmap to glyph atlas texture.
if (glyphSize.x > 0 && glyphSize.y > 0) { if (glyphSize.x > 0 && glyphSize.y > 0) {
mRenderer->updateTexture(tex->textureId, 0, Renderer::TextureType::RED, cursor.x, cursor.y, mRenderer->updateTexture(tex->textureId, 0, Renderer::TextureType::RED, cursor.x, cursor.y,
glyphSize.x, glyphSize.y, glyphSlot->bitmap.buffer); glyphSize.x, glyphSize.y, glyphSlot->bitmap.buffer);

View file

@ -3,8 +3,7 @@
// ES-DE Frontend // ES-DE Frontend
// Font.h // Font.h
// //
// Loading, unloading, caching, shaping and rendering of fonts. // Font management and text shaping and rendering.
// Also functions for text wrapping and similar.
// //
#ifndef ES_CORE_RESOURCES_FONT_H #ifndef ES_CORE_RESOURCES_FONT_H
@ -33,8 +32,6 @@ class TextCache;
#define FONT_PATH_REGULAR ":/fonts/Akrobat-SemiBold.ttf" #define FONT_PATH_REGULAR ":/fonts/Akrobat-SemiBold.ttf"
#define FONT_PATH_BOLD ":/fonts/Akrobat-Bold.ttf" #define FONT_PATH_BOLD ":/fonts/Akrobat-Bold.ttf"
// A TrueType Font renderer that uses FreeType and OpenGL.
// The library is automatically initialized when it's needed.
class Font : public IReloadable class Font : public IReloadable
{ {
public: public:
@ -135,9 +132,9 @@ public:
const float sizeMultiplier = 1.0f, const float sizeMultiplier = 1.0f,
const bool fontSizeDimmed = false); const bool fontSizeDimmed = false);
// Returns an approximation of VRAM used by this font's texture (in bytes). // Returns an approximation of VRAM used by the glyph atlas textures for this font object.
size_t getMemUsage() const; size_t getMemUsage() const;
// Returns an approximation of total VRAM used by font textures (in bytes). // Returns an approximation of VRAM used by the glyph atlas textures for all font objects.
static size_t getTotalMemUsage(); static size_t getTotalMemUsage();
private: private:
@ -155,11 +152,9 @@ private:
bool findEmpty(const glm::ivec2& size, glm::ivec2& cursorOut); bool findEmpty(const glm::ivec2& size, glm::ivec2& cursorOut);
// You must call initTexture() after creating a FontTexture to get a textureId. // You must call initTexture() after creating a FontTexture to get a textureId.
// Initializes the OpenGL texture according to this FontTexture's settings,
// updating textureId.
void initTexture(); void initTexture();
// Deinitializes any existing OpenGL textures, is automatically called in destructor. // Deinitializes all glyph atlas textures.
void deinitTexture(); void deinitTexture();
}; };
@ -217,7 +212,7 @@ private:
// Shape text using HarfBuzz. // Shape text using HarfBuzz.
void shapeText(const std::string& text, std::vector<ShapeSegment>& segmentsHB); void shapeText(const std::string& text, std::vector<ShapeSegment>& segmentsHB);
// Completely recreate the texture data for all textures based on mGlyphs information. // Completely recreate the texture data for all glyph atlas entries.
void rebuildTextures(); void rebuildTextures();
void unloadTextures(); void unloadTextures();
@ -256,12 +251,7 @@ private:
int mMaxGlyphHeight; int mMaxGlyphHeight;
}; };
// Used to store a sort of "pre-rendered" string. // Caching of shaped and rendered text.
// When a TextCache is constructed (Font::buildTextCache()), the vertices and texture coordinates
// of the string are calculated and stored in the TextCache object. Rendering a previously
// constructed TextCache (Font::renderTextCache) every frame is much faster than rebuilding
// one every frame. Keep in mind you still need the Font object to render a TextCache (as the
// Font holds the OpenGL texture), and if a Font changes your TextCache may become invalid.
class TextCache class TextCache
{ {
public: public: