Refactor font handling code

* Add cache for loaded fonts
 * Replace `const std::string &` with `std::string_view` in print
 * Remove static load method
 * Reformat with clang-format
 * Return bool from celestiacore::set*Font
do-not-use-first-sujection
Hleb Valoshka 2022-01-01 22:55:51 +02:00
parent 3c9334ece9
commit f969b37c3e
5 changed files with 357 additions and 278 deletions

View File

@ -4053,14 +4053,10 @@ bool CelestiaCore::initRenderer()
if (font == nullptr) if (font == nullptr)
cout << _("Error loading font; text will not be visible.\n"); cout << _("Error loading font; text will not be visible.\n");
else
font->buildTexture();
if (!config->titleFont.empty()) if (!config->titleFont.empty())
titleFont = LoadFontHelper(renderer, config->titleFont); titleFont = LoadFontHelper(renderer, config->titleFont);
if (titleFont != nullptr) if (titleFont == nullptr)
titleFont->buildTexture();
else
titleFont = font; titleFont = font;
// Set up the overlay // Set up the overlay
@ -4074,15 +4070,7 @@ bool CelestiaCore::initRenderer()
else else
{ {
auto labelFont = LoadFontHelper(renderer, config->labelFont); auto labelFont = LoadFontHelper(renderer, config->labelFont);
if (labelFont == nullptr) renderer->setFont(Renderer::FontNormal, labelFont == nullptr ? font : labelFont);
{
renderer->setFont(Renderer::FontNormal, font);
}
else
{
labelFont->buildTexture();
renderer->setFont(Renderer::FontNormal, labelFont);
}
} }
renderer->setFont(Renderer::FontLarge, titleFont); renderer->setFont(Renderer::FontLarge, titleFont);
@ -4286,26 +4274,34 @@ CelestiaCore::TextDisplayHandler* CelestiaCore::getTextDisplayHandler() const
return textDisplayHandler; return textDisplayHandler;
} }
void CelestiaCore::setFont(const fs::path& fontPath, int collectionIndex, int fontSize) bool CelestiaCore::setFont(const fs::path& fontPath, int collectionIndex, int fontSize)
{ {
font = LoadTextureFont(renderer, fontPath, collectionIndex, fontSize); if (auto f = LoadTextureFont(renderer, fontPath, collectionIndex, fontSize); f != nullptr)
if (font != nullptr) {
font->buildTexture(); font = f;
return true;
}
return false;
} }
void CelestiaCore::setTitleFont(const fs::path& fontPath, int collectionIndex, int fontSize) bool CelestiaCore::setTitleFont(const fs::path& fontPath, int collectionIndex, int fontSize)
{ {
titleFont = LoadTextureFont(renderer, fontPath, collectionIndex, fontSize); if (auto f = LoadTextureFont(renderer, fontPath, collectionIndex, fontSize); f != nullptr)
if (titleFont != nullptr) {
titleFont->buildTexture(); titleFont = f;
return true;
}
return false;
} }
void CelestiaCore::setRendererFont(const fs::path& fontPath, int collectionIndex, int fontSize, Renderer::FontStyle fontStyle) bool CelestiaCore::setRendererFont(const fs::path& fontPath, int collectionIndex, int fontSize, Renderer::FontStyle fontStyle)
{ {
auto f = LoadTextureFont(renderer, fontPath, collectionIndex, fontSize); if (auto f = LoadTextureFont(renderer, fontPath, collectionIndex, fontSize); f != nullptr)
if (f != nullptr) {
f->buildTexture(); renderer->setFont(fontStyle, f);
renderer->setFont(fontStyle, f); return true;
}
return false;
} }
void CelestiaCore::clearFonts() void CelestiaCore::clearFonts()

View File

@ -368,9 +368,9 @@ class CelestiaCore // : public Watchable<CelestiaCore>
void setTextDisplayHandler(TextDisplayHandler*); void setTextDisplayHandler(TextDisplayHandler*);
TextDisplayHandler* getTextDisplayHandler() const; TextDisplayHandler* getTextDisplayHandler() const;
void setFont(const fs::path& fontPath, int collectionIndex, int fontSize); bool setFont(const fs::path& fontPath, int collectionIndex, int fontSize);
void setTitleFont(const fs::path& fontPath, int collectionIndex, int fontSize); bool setTitleFont(const fs::path& fontPath, int collectionIndex, int fontSize);
void setRendererFont(const fs::path& fontPath, int collectionIndex, int fontSize, Renderer::FontStyle fontStyle); bool setRendererFont(const fs::path& fontPath, int collectionIndex, int fontSize, Renderer::FontStyle fontStyle);
void clearFonts(); void clearFonts();
void toggleReferenceMark(const std::string& refMark, Selection sel = Selection()); void toggleReferenceMark(const std::string& refMark, Selection sel = Selection());

View File

@ -2296,7 +2296,6 @@ static int celestia_loadfont(lua_State* l)
CelestiaCore* appCore = getAppCore(l, AllErrors); CelestiaCore* appCore = getAppCore(l, AllErrors);
auto font = LoadTextureFont(appCore->getRenderer(), s); auto font = LoadTextureFont(appCore->getRenderer(), s);
if (font == nullptr) return 0; if (font == nullptr) return 0;
font->buildTexture();
return celx.pushClass(font); return celx.pushClass(font);
} }

View File

@ -1,6 +1,6 @@
// truetypefont.cpp // truetypefont.cpp
// //
// Copyright (C) 2019, Celestia Development Team // Copyright (C) 2019-2022, Celestia Development Team
// //
// This program is free software; you can redistribute it and/or // This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License // modify it under the terms of the GNU General Public License
@ -9,13 +9,15 @@
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <iostream> #include <celcompat/charconv.h>
#include <vector>
#include <celutil/logger.h>
#include <celutil/utf8.h>
#include <celengine/glsupport.h> #include <celengine/glsupport.h>
#include <celengine/render.h> #include <celengine/render.h>
#include <celutil/logger.h>
#include <celutil/utf8.h>
#include <ft2build.h> #include <ft2build.h>
#include <map>
#include <system_error>
#include <vector>
#include FT_FREETYPE_H #include FT_FREETYPE_H
#include "truetypefont.h" #include "truetypefont.h"
@ -25,26 +27,24 @@
#include <fstream> #include <fstream>
#endif #endif
using namespace std; using celestia::compat::from_chars;
using celestia::util::GetLogger; using celestia::util::GetLogger;
static FT_Library ft = nullptr;
struct Glyph struct Glyph
{ {
wchar_t ch; wchar_t ch;
int ax; // advance.x int ax; // advance.x
int ay; // advance.y int ay; // advance.y
int bw; // bitmap.width; unsigned int bw; // bitmap.width;
int bh; // bitmap.height; unsigned int bh; // bitmap.height;
int bl; // bitmap_left; int bl; // bitmap_left;
int bt; // bitmap_top; int bt; // bitmap_top;
float tx; // x offset of glyph in texture coordinates float tx; // x offset of glyph in texture coordinates
float ty; // y offset of glyph in texture coordinates float ty; // y offset of glyph in texture coordinates
}; };
struct UnicodeBlock struct UnicodeBlock
@ -56,75 +56,82 @@ struct TextureFontPrivate
{ {
struct FontVertex struct FontVertex
{ {
FontVertex(float _x, float _y, float _u, float _v) : FontVertex(float _x, float _y, float _u, float _v) : x(_x), y(_y), u(_u), v(_v)
x(_x), y(_y), u(_u), v(_v) {
{} }
float x, y; float x, y;
float u, v; float u, v;
}; };
TextureFontPrivate() = delete;
TextureFontPrivate(const Renderer *renderer); TextureFontPrivate(const Renderer *renderer);
~TextureFontPrivate(); ~TextureFontPrivate();
TextureFontPrivate(const TextureFontPrivate&) = default; TextureFontPrivate() = delete;
TextureFontPrivate(TextureFontPrivate&&) = default; TextureFontPrivate(const TextureFontPrivate &) = default;
TextureFontPrivate& operator=(const TextureFontPrivate&) = default; TextureFontPrivate(TextureFontPrivate &&) = default;
TextureFontPrivate& operator=(TextureFontPrivate&&) = default; TextureFontPrivate &operator=(const TextureFontPrivate &) = default;
TextureFontPrivate &operator=(TextureFontPrivate &&) = default;
float render(const string &s, float x, float y); float render(std::string_view s, float x, float y);
float render(wchar_t ch, float xoffset, float yoffset); float render(wchar_t ch, float xoffset, float yoffset);
bool buildAtlas(); bool buildAtlas();
void computeTextureSize(); void computeTextureSize();
bool loadGlyphInfo(wchar_t, Glyph&); bool loadGlyphInfo(wchar_t /*ch*/, Glyph & /*c*/) const;
void initCommonGlyphs(); void initCommonGlyphs();
int getCommonGlyphsCount(); int getCommonGlyphsCount();
Glyph& getGlyph(wchar_t); Glyph & getGlyph(wchar_t /*ch*/);
Glyph& getGlyph(wchar_t, wchar_t); Glyph & getGlyph(wchar_t /*ch*/, wchar_t /*fallback*/);
int toPos(wchar_t) const; [[nodiscard]] int toPos(wchar_t /*ch*/) const;
void optimize(); void optimize();
CelestiaGLProgram* getProgram(); CelestiaGLProgram *getProgram();
void flush(); void flush();
const Renderer *m_renderer; const Renderer *m_renderer;
CelestiaGLProgram *m_prog { nullptr }; CelestiaGLProgram *m_prog{ nullptr };
FT_Face m_face; // font face FT_Face m_face; // font face
int m_maxAscent; int m_maxAscent{ 0 };
int m_maxDescent; int m_maxDescent{ 0 };
int m_maxWidth; int m_maxWidth{ 0 };
int m_texWidth; int m_texWidth{ 0 };
int m_texHeight; int m_texHeight{ 0 };
GLuint m_texName { 0 }; // texture object GLuint m_texName{ 0 }; // texture object
vector<Glyph> m_glyphs; // character information std::vector<Glyph> m_glyphs; // character information
GLint m_maxTextureSize; // max supported texture size GLint m_maxTextureSize; // max supported texture size
array<UnicodeBlock, 2> m_unicodeBlocks; std::array<UnicodeBlock, 2> m_unicodeBlocks;
int m_commonGlyphsCount { 0 }; int m_commonGlyphsCount{ 0 };
int m_inserted { 0 }; int m_inserted{ 0 };
Eigen::Matrix4f m_projection; Eigen::Matrix4f m_projection;
Eigen::Matrix4f m_modelView; Eigen::Matrix4f m_modelView;
bool m_shaderInUse { false }; bool m_shaderInUse{ false };
vector<FontVertex> m_fontVertices; std::vector<FontVertex> m_fontVertices;
}; };
inline float pt_to_px(float pt, int dpi = 96) namespace
{ {
return dpi == 0 ? pt : pt / 72.0 * dpi;
inline float
pt_to_px(float pt, int dpi = 96)
{
return dpi == 0 ? pt : pt / 72.0f * static_cast<float>(dpi);
} }
Glyph g_badGlyph = { 0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f };
} // namespace
/* /*
first = ((c / 32) + 1) * 32 == c & ~0xdf first = ((c / 32) + 1) * 32 == c & ~0xdf
last = first + 32 last = first + 32
*/ */
TextureFontPrivate::TextureFontPrivate(const Renderer *renderer) : TextureFontPrivate::TextureFontPrivate(const Renderer *renderer) : m_renderer(renderer)
m_renderer(renderer)
{ {
m_unicodeBlocks[0] = { 0x0020, 0x007E }; // Basic Latin m_unicodeBlocks[0] = { 0x0020, 0x007E }; // Basic Latin
m_unicodeBlocks[1] = { 0x03B1, 0x03CF }; // Lower case Greek m_unicodeBlocks[1] = { 0x03B1, 0x03CF }; // Lower case Greek
@ -134,13 +141,12 @@ TextureFontPrivate::TextureFontPrivate(const Renderer *renderer) :
TextureFontPrivate::~TextureFontPrivate() TextureFontPrivate::~TextureFontPrivate()
{ {
if (m_face) if (m_face != nullptr) FT_Done_Face(m_face);
FT_Done_Face(m_face); if (m_texName != 0) glDeleteTextures(1, &m_texName);
if (m_texName != 0)
glDeleteTextures(1, &m_texName);
} }
bool TextureFontPrivate::loadGlyphInfo(wchar_t ch, Glyph &c) bool
TextureFontPrivate::loadGlyphInfo(wchar_t ch, Glyph &c) const
{ {
FT_GlyphSlot g = m_face->glyph; FT_GlyphSlot g = m_face->glyph;
if (FT_Load_Char(m_face, ch, FT_LOAD_RENDER) != 0) if (FT_Load_Char(m_face, ch, FT_LOAD_RENDER) != 0)
@ -159,10 +165,10 @@ bool TextureFontPrivate::loadGlyphInfo(wchar_t ch, Glyph &c)
return true; return true;
} }
void TextureFontPrivate::initCommonGlyphs() void
TextureFontPrivate::initCommonGlyphs()
{ {
if (m_glyphs.size() > 0) if (!m_glyphs.empty()) return;
return;
m_glyphs.reserve(256); m_glyphs.reserve(256);
@ -178,12 +184,13 @@ void TextureFontPrivate::initCommonGlyphs()
} }
} }
void TextureFontPrivate::computeTextureSize() void
TextureFontPrivate::computeTextureSize()
{ {
int roww = 0; int roww = 0;
int rowh = 0; int rowh = 0;
int w = 0; int w = 0;
int h = 0; int h = 0;
// Find minimum size for a texture holding all visible ASCII characters // Find minimum size for a texture holding all visible ASCII characters
for (const auto &c : m_glyphs) for (const auto &c : m_glyphs)
@ -192,23 +199,24 @@ void TextureFontPrivate::computeTextureSize()
if (roww + c.bw + 1 >= m_maxTextureSize) if (roww + c.bw + 1 >= m_maxTextureSize)
{ {
w = max(w, roww); w = std::max(w, roww);
h += rowh; h += rowh;
roww = 0; roww = 0;
rowh = 0; rowh = 0;
} }
roww += c.bw + 1; roww += c.bw + 1;
rowh = max(rowh, (int)c.bh); rowh = std::max(rowh, static_cast<int>(c.bh));
} }
w = max(w, roww); w = std::max(w, roww);
h += rowh; h += rowh;
m_texWidth = w; m_texWidth = w;
m_texHeight = h; m_texHeight = h;
} }
bool TextureFontPrivate::buildAtlas() bool
TextureFontPrivate::buildAtlas()
{ {
FT_GlyphSlot g = m_face->glyph; FT_GlyphSlot g = m_face->glyph;
@ -217,14 +225,20 @@ bool TextureFontPrivate::buildAtlas()
// Create a texture that will be used to hold all glyphs // Create a texture that will be used to hold all glyphs
glActiveTexture(GL_TEXTURE0); glActiveTexture(GL_TEXTURE0);
if (m_texName != 0) if (m_texName != 0) glDeleteTextures(1, &m_texName);
glDeleteTextures(1, &m_texName);
glGenTextures(1, &m_texName); glGenTextures(1, &m_texName);
if (m_texName == 0) if (m_texName == 0) return false;
return false;
glBindTexture(GL_TEXTURE_2D, m_texName); glBindTexture(GL_TEXTURE_2D, m_texName);
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, m_texWidth, m_texHeight, 0, GL_ALPHA, GL_UNSIGNED_BYTE, 0); glTexImage2D(GL_TEXTURE_2D,
0,
GL_ALPHA,
m_texWidth,
m_texHeight,
0,
GL_ALPHA,
GL_UNSIGNED_BYTE,
nullptr);
// We require 1 byte alignment when uploading texture data // We require 1 byte alignment when uploading texture data
glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
@ -247,42 +261,53 @@ bool TextureFontPrivate::buildAtlas()
{ {
if (c.ch == 0) continue; // skip bad glyphs if (c.ch == 0) continue; // skip bad glyphs
if (FT_Load_Char(m_face, c.ch, FT_LOAD_RENDER)) if (FT_Load_Char(m_face, c.ch, FT_LOAD_RENDER) != 0)
{ {
GetLogger()->warn("Loading character {:x} failed!\n", static_cast<unsigned>(c.ch)); GetLogger()->warn("Loading character {:x} failed!\n", static_cast<unsigned>(c.ch));
c.ch = 0; c.ch = 0;
continue; continue;
} }
if (ox + int(g->bitmap.width) > int(m_texWidth)) if (ox + int(g->bitmap.width) > int(m_texWidth))
{ {
oy += rowh; oy += rowh;
rowh = 0; rowh = 0;
ox = 0; ox = 0;
} }
glTexSubImage2D(GL_TEXTURE_2D, 0, ox, oy, g->bitmap.width, g->bitmap.rows, GL_ALPHA, GL_UNSIGNED_BYTE, g->bitmap.buffer); glTexSubImage2D(GL_TEXTURE_2D,
c.tx = (float)ox / (float)m_texWidth; 0,
c.ty = (float)oy / (float)m_texHeight; ox,
oy,
g->bitmap.width,
g->bitmap.rows,
GL_ALPHA,
GL_UNSIGNED_BYTE,
g->bitmap.buffer);
c.tx = static_cast<float>(ox) / static_cast<float>(m_texWidth);
c.ty = static_cast<float>(oy) / static_cast<float>(m_texHeight);
rowh = max(rowh, (int)g->bitmap.rows); rowh = std::max(rowh, static_cast<int>(g->bitmap.rows));
ox += g->bitmap.width + 1; ox += g->bitmap.width + 1;
} }
#if DUMP_TEXTURE #if DUMP_TEXTURE
fmt::printf(cout/*cerr*/, "Generated a {} x {} ({} kb) texture atlas\n", m_texWidth, m_texHeight, m_texWidth * m_texHeight / 1024); fmt::print("Generated a {} x {} ({} kb) texture atlas\n",
size_t img_size = sizeof(uint8_t) * m_texWidth * m_texHeight * 4; m_texWidth, m_texHeight,
uint8_t *raw_img = new uint8_t[img_size]; m_texWidth * m_texHeight / 1024);
size_t img_size = sizeof(uint8_t) * m_texWidth * m_texHeight * 4;
uint8_t *raw_img = new uint8_t[img_size];
glGetTexImage(GL_TEXTURE_2D, 0, GL_BGRA, GL_UNSIGNED_BYTE, raw_img); glGetTexImage(GL_TEXTURE_2D, 0, GL_BGRA, GL_UNSIGNED_BYTE, raw_img);
ofstream f(fmt::format("/tmp/texture_{}x{}.data", m_texWidth, m_texHeight), ios::binary); ofstream f(fmt::format("/tmp/texture_{}x{}.data", m_texWidth, m_texHeight), ios::binary);
f.write(reinterpret_cast<char*>(raw_img), img_size); f.write(reinterpret_cast<char *>(raw_img), img_size);
f.close(); f.close();
delete[] raw_img; delete[] raw_img;
#endif #endif
return true; return true;
} }
int TextureFontPrivate::getCommonGlyphsCount() int
TextureFontPrivate::getCommonGlyphsCount()
{ {
if (m_commonGlyphsCount == 0) if (m_commonGlyphsCount == 0)
{ {
@ -292,17 +317,16 @@ int TextureFontPrivate::getCommonGlyphsCount()
return m_commonGlyphsCount; return m_commonGlyphsCount;
} }
int TextureFontPrivate::toPos(wchar_t ch) const int
TextureFontPrivate::toPos(wchar_t ch) const
{ {
int pos = 0; int pos = 0;
if (ch > m_unicodeBlocks.back().last) if (ch > m_unicodeBlocks.back().last) return -1;
return -1;
for (const auto &r : m_unicodeBlocks) for (const auto &r : m_unicodeBlocks)
{ {
if (ch < r.first) if (ch < r.first) return -1;
return -1;
if (ch <= r.last) if (ch <= r.last)
{ {
@ -314,22 +338,22 @@ int TextureFontPrivate::toPos(wchar_t ch) const
return -1; return -1;
} }
Glyph& TextureFontPrivate::getGlyph(wchar_t ch, wchar_t fallback) Glyph &
TextureFontPrivate::getGlyph(wchar_t ch, wchar_t fallback)
{ {
auto &g = getGlyph(ch); auto &g = getGlyph(ch);
return g.ch == ch ? g : getGlyph(fallback); return g.ch == ch ? g : getGlyph(fallback);
} }
Glyph g_badGlyph = {0, 0, 0, 0, 0, 0, 0, 0.0f, 0.0f}; Glyph &
Glyph& TextureFontPrivate::getGlyph(wchar_t ch) TextureFontPrivate::getGlyph(wchar_t ch)
{ {
auto pos = toPos(ch); if (auto pos = toPos(ch); pos != -1)
if (pos != -1)
return m_glyphs[pos]; return m_glyphs[pos];
auto it = find_if(m_glyphs.begin() + getCommonGlyphsCount(), auto it = find_if(m_glyphs.begin() + getCommonGlyphsCount(),
m_glyphs.end(), m_glyphs.end(),
[ch](Glyph &g) { return g.ch == ch; }); [ch](const Glyph &g) { return g.ch == ch; });
if (it != m_glyphs.end()) if (it != m_glyphs.end())
return *it; return *it;
@ -341,14 +365,14 @@ Glyph& TextureFontPrivate::getGlyph(wchar_t ch)
flush(); // render text to avoid garbled output due to changed texture flush(); // render text to avoid garbled output due to changed texture
m_glyphs.push_back(c); m_glyphs.push_back(c);
if (++m_inserted == 10) if (++m_inserted == 10) optimize();
optimize();
buildAtlas(); buildAtlas();
return m_glyphs.back(); return m_glyphs.back();
} }
void TextureFontPrivate::optimize() void
TextureFontPrivate::optimize()
{ {
m_inserted = 0; m_inserted = 0;
} }
@ -358,34 +382,33 @@ void TextureFontPrivate::optimize()
* Rendering starts at coordinates (x, y), z is always 0. * Rendering starts at coordinates (x, y), z is always 0.
* The pixel coordinates that the FreeType2 library uses are scaled by (sx, sy). * The pixel coordinates that the FreeType2 library uses are scaled by (sx, sy).
*/ */
float TextureFontPrivate::render(const string &s, float x, float y) float
TextureFontPrivate::render(std::string_view s, float x, float y)
{ {
if (m_texName == 0) if (m_texName == 0) return 0;
return 0;
// Use the texture containing the atlas // Use the texture containing the atlas
glBindTexture(GL_TEXTURE_2D, m_texName); glBindTexture(GL_TEXTURE_2D, m_texName);
// Loop through all characters // Loop through all characters
int len = s.length(); int len = s.length();
bool validChar = true; bool validChar = true;
int i = 0; int i = 0;
while (i < len && validChar) while (i < len && validChar)
{ {
wchar_t ch = 0; wchar_t ch = 0;
validChar = UTF8Decode(s, i, ch); validChar = UTF8Decode(s, i, ch);
if (!validChar) if (!validChar) break;
break;
i += UTF8EncodedSize(ch); i += UTF8EncodedSize(ch);
auto& g = getGlyph(ch, L'?'); auto &g = getGlyph(ch, L'?');
// Calculate the vertex and texture coordinates // Calculate the vertex and texture coordinates
const float x1 = x + g.bl; const float x1 = x + g.bl;
const float y1 = y + g.bt - g.bh; const float y1 = y + g.bt - g.bh;
const float w = g.bw; const float w = g.bw;
const float h = g.bh; const float h = g.bh;
const float x2 = x1 + w; const float x2 = x1 + w;
const float y2 = y1 + h; const float y2 = y1 + h;
@ -394,27 +417,26 @@ float TextureFontPrivate::render(const string &s, float x, float y)
y += g.ay; y += g.ay;
// Skip glyphs that have no pixels // Skip glyphs that have no pixels
if (g.bw == 0 || g.bh == 0) if (g.bw == 0 || g.bh == 0) continue;
continue;
const float tx1 = g.tx; const float tx1 = g.tx;
const float ty1 = g.ty; const float ty1 = g.ty;
const float tx2 = tx1 + w / m_texWidth; const float tx2 = tx1 + w / m_texWidth;
const float ty2 = ty1 + h / m_texHeight; const float ty2 = ty1 + h / m_texHeight;
m_fontVertices.emplace_back(FontVertex(x1, y1, tx1, ty2)); m_fontVertices.emplace_back(x1, y1, tx1, ty2);
m_fontVertices.emplace_back(FontVertex(x2, y1, tx2, ty2)); m_fontVertices.emplace_back(x2, y1, tx2, ty2);
m_fontVertices.emplace_back(FontVertex(x1, y2, tx1, ty1)); m_fontVertices.emplace_back(x1, y2, tx1, ty1);
m_fontVertices.emplace_back(FontVertex(x2, y2, tx2, ty1)); m_fontVertices.emplace_back(x2, y2, tx2, ty1);
} }
return x; return x;
} }
float TextureFontPrivate::render(wchar_t ch, float xoffset, float yoffset) float
TextureFontPrivate::render(wchar_t ch, float xoffset, float yoffset)
{ {
auto &g = getGlyph(ch, L'?');
auto& g = getGlyph(ch, L'?');
// Calculate the vertex and texture coordinates // Calculate the vertex and texture coordinates
const float x1 = xoffset + g.bl; const float x1 = xoffset + g.bl;
@ -427,32 +449,30 @@ float TextureFontPrivate::render(wchar_t ch, float xoffset, float yoffset)
const float tx2 = tx1 + static_cast<float>(g.bw) / m_texWidth; const float tx2 = tx1 + static_cast<float>(g.bw) / m_texWidth;
const float ty2 = ty1 + static_cast<float>(g.bh) / m_texHeight; const float ty2 = ty1 + static_cast<float>(g.bh) / m_texHeight;
m_fontVertices.emplace_back(FontVertex(x1, y1, tx1, ty2)); m_fontVertices.emplace_back(x1, y1, tx1, ty2);
m_fontVertices.emplace_back(FontVertex(x2, y1, tx2, ty2)); m_fontVertices.emplace_back(x2, y1, tx2, ty2);
m_fontVertices.emplace_back(FontVertex(x1, y2, tx1, ty1)); m_fontVertices.emplace_back(x1, y2, tx1, ty1);
m_fontVertices.emplace_back(FontVertex(x2, y2, tx2, ty1)); m_fontVertices.emplace_back(x2, y2, tx2, ty1);
return g.ax; return g.ax;
} }
CelestiaGLProgram* TextureFontPrivate::getProgram() CelestiaGLProgram *
TextureFontPrivate::getProgram()
{ {
if (m_prog != nullptr) if (m_prog != nullptr) return m_prog;
return m_prog;
m_prog = m_renderer->getShaderManager().getShader("text"); m_prog = m_renderer->getShaderManager().getShader("text");
return m_prog; return m_prog;
} }
void TextureFontPrivate::flush() void
TextureFontPrivate::flush()
{ {
if (m_fontVertices.size() < 4) if (m_fontVertices.size() < 4) return;
return;
vector<unsigned short> indexes; std::vector<unsigned short> indexes;
indexes.reserve(m_fontVertices.size() / 4 * 6); indexes.reserve(m_fontVertices.size() / 4 * 6);
for (unsigned short index = 0; for (unsigned short index = 0; index < static_cast<unsigned short>(m_fontVertices.size()); index += 4)
index < (unsigned short) m_fontVertices.size();
index += 4)
{ {
indexes.push_back(index + 0); indexes.push_back(index + 0);
indexes.push_back(index + 1); indexes.push_back(index + 1);
@ -465,9 +485,17 @@ void TextureFontPrivate::flush()
glEnableVertexAttribArray(CelestiaGLProgram::VertexCoordAttributeIndex); glEnableVertexAttribArray(CelestiaGLProgram::VertexCoordAttributeIndex);
glEnableVertexAttribArray(CelestiaGLProgram::TextureCoord0AttributeIndex); glEnableVertexAttribArray(CelestiaGLProgram::TextureCoord0AttributeIndex);
glVertexAttribPointer(CelestiaGLProgram::VertexCoordAttributeIndex, glVertexAttribPointer(CelestiaGLProgram::VertexCoordAttributeIndex,
2, GL_FLOAT, GL_FALSE, sizeof(FontVertex), &m_fontVertices[0].x); 2,
GL_FLOAT,
GL_FALSE,
sizeof(FontVertex),
&m_fontVertices[0].x);
glVertexAttribPointer(CelestiaGLProgram::TextureCoord0AttributeIndex, glVertexAttribPointer(CelestiaGLProgram::TextureCoord0AttributeIndex,
2, GL_FLOAT, GL_FALSE, sizeof(FontVertex), &m_fontVertices[0].u); 2,
GL_FLOAT,
GL_FALSE,
sizeof(FontVertex),
&m_fontVertices[0].u);
glDrawElements(GL_TRIANGLES, indexes.size(), GL_UNSIGNED_SHORT, indexes.data()); glDrawElements(GL_TRIANGLES, indexes.size(), GL_UNSIGNED_SHORT, indexes.data());
glDisableVertexAttribArray(CelestiaGLProgram::VertexCoordAttributeIndex); glDisableVertexAttribArray(CelestiaGLProgram::VertexCoordAttributeIndex);
glDisableVertexAttribArray(CelestiaGLProgram::TextureCoord0AttributeIndex); glDisableVertexAttribArray(CelestiaGLProgram::TextureCoord0AttributeIndex);
@ -475,17 +503,11 @@ void TextureFontPrivate::flush()
m_fontVertices.clear(); m_fontVertices.clear();
} }
TextureFont::TextureFont(const Renderer *renderer) : TextureFont::TextureFont(const Renderer *renderer) :
impl(new TextureFontPrivate(renderer)) impl(std::make_unique<TextureFontPrivate>(renderer))
{ {
} }
TextureFont::~TextureFont()
{
delete impl;
}
/** /**
* Render a single character of the font with offset * Render a single character of the font with offset
* *
@ -496,7 +518,8 @@ TextureFont::~TextureFont()
* @param xoffset -- horizontal offset * @param xoffset -- horizontal offset
* @param yoffset -- vertical offset * @param yoffset -- vertical offset
*/ */
float TextureFont::render(wchar_t ch, float xoffset, float yoffset) const float
TextureFont::render(wchar_t ch, float xoffset, float yoffset) const
{ {
return impl->render(ch, xoffset, yoffset); return impl->render(ch, xoffset, yoffset);
} }
@ -511,7 +534,8 @@ float TextureFont::render(wchar_t ch, float xoffset, float yoffset) const
* @param xoffset -- horizontal offset * @param xoffset -- horizontal offset
* @param yoffset -- vertical offset * @param yoffset -- vertical offset
*/ */
float TextureFont::render(const string &s, float xoffset, float yoffset) const float
TextureFont::render(std::string_view s, float xoffset, float yoffset) const
{ {
return impl->render(s, xoffset, yoffset); return impl->render(s, xoffset, yoffset);
} }
@ -524,69 +548,92 @@ float TextureFont::render(const string &s, float xoffset, float yoffset) const
* @param s -- string to calculate width * @param s -- string to calculate width
* @return string width in pixels * @return string width in pixels
*/ */
int TextureFont::getWidth(const string& s) const int
TextureFont::getWidth(std::string_view s) const
{ {
int width = 0; int width = 0;
int len = s.length(); int len = s.length();
bool validChar = true; bool validChar = true;
int i = 0; int i = 0;
while (i < len && validChar) while (i < len && validChar)
{ {
wchar_t ch = 0; wchar_t ch = 0;
validChar = UTF8Decode(s, i, ch); validChar = UTF8Decode(s, i, ch);
if (!validChar) if (!validChar) break;
break;
i += UTF8EncodedSize(ch); i += UTF8EncodedSize(ch);
auto& g = impl->getGlyph(ch, L'?'); auto &g = impl->getGlyph(ch, L'?');
width += g.ax; width += g.ax;
} }
return width; return width;
} }
int TextureFont::getHeight() const /**
* Return line height for the current font as sum of the maximal ascent and the
* maximal descent.
*/
int
TextureFont::getHeight() const
{ {
return impl->m_maxAscent + impl->m_maxDescent; return impl->m_maxAscent + impl->m_maxDescent;
} }
int TextureFont::getMaxWidth() const /**
* Return the maximal character width for the current font.
*/
int
TextureFont::getMaxWidth() const
{ {
return impl->m_maxWidth; return impl->m_maxWidth;
} }
int TextureFont::getMaxAscent() const /**
* Return the maximal ascent for the current font.
*/
int
TextureFont::getMaxAscent() const
{ {
return impl->m_maxAscent; return impl->m_maxAscent;
} }
void TextureFont::setMaxAscent(int _maxAscent) /**
* Set the maximal ascent for the current font.
*/
void
TextureFont::setMaxAscent(int _maxAscent)
{ {
impl->m_maxAscent = _maxAscent; impl->m_maxAscent = _maxAscent;
} }
int TextureFont::getMaxDescent() const /**
* Return the maximal descent for the current font.
*/
int
TextureFont::getMaxDescent() const
{ {
return impl->m_maxDescent; return impl->m_maxDescent;
} }
void TextureFont::setMaxDescent(int _maxDescent) /**
* Set the maximal descent for the current font.
*/
void
TextureFont::setMaxDescent(int _maxDescent)
{ {
impl->m_maxDescent = _maxDescent; impl->m_maxDescent = _maxDescent;
} }
int TextureFont::getTextureName() const /**
{ * Use the current font for text rendering.
return impl->m_texName; */
} void
TextureFont::bind()
void TextureFont::bind()
{ {
auto *prog = impl->getProgram(); auto *prog = impl->getProgram();
if (prog == nullptr) if (prog == nullptr) return;
return;
if (impl->m_texName != 0) if (impl->m_texName != 0)
{ {
@ -594,16 +641,20 @@ void TextureFont::bind()
glBindTexture(GL_TEXTURE_2D, impl->m_texName); glBindTexture(GL_TEXTURE_2D, impl->m_texName);
prog->use(); prog->use();
prog->samplerParam("atlasTex") = 0; prog->samplerParam("atlasTex") = 0;
impl->m_shaderInUse = true; impl->m_shaderInUse = true;
prog->setMVPMatrices(impl->m_projection, impl->m_modelView); prog->setMVPMatrices(impl->m_projection, impl->m_modelView);
} }
} }
void TextureFont::setMVPMatrices(const Eigen::Matrix4f& p, const Eigen::Matrix4f& m) /**
* Assign Projection and ModelView matrices for the current font.
*/
void
TextureFont::setMVPMatrices(const Eigen::Matrix4f &p, const Eigen::Matrix4f &m)
{ {
impl->m_projection = p; impl->m_projection = p;
impl->m_modelView = m; impl->m_modelView = m;
auto *prog = impl->getProgram(); auto *prog = impl->getProgram();
if (prog != nullptr && impl->m_shaderInUse) if (prog != nullptr && impl->m_shaderInUse)
{ {
flush(); flush();
@ -611,29 +662,40 @@ void TextureFont::setMVPMatrices(const Eigen::Matrix4f& p, const Eigen::Matrix4f
} }
} }
void TextureFont::unbind() /**
* Stop the current font usage.
*/
void
TextureFont::unbind()
{ {
flush(); flush();
impl->m_shaderInUse = false; impl->m_shaderInUse = false;
} }
short TextureFont::getAdvance(wchar_t ch) const /**
* Return the advance for the wide character `ch`.
*/
short
TextureFont::getAdvance(wchar_t ch) const
{ {
auto& g = impl->getGlyph(ch, L'?'); auto &g = impl->getGlyph(ch, L'?');
return g.ax; return g.ax;
} }
bool TextureFont::buildTexture() /**
{ * Perform all delayed text rendering operations.
return true; */
} void
TextureFont::flush()
void TextureFont::flush()
{ {
impl->flush(); impl->flush();
} }
TextureFont* TextureFont::load(const Renderer *r, const fs::path &path, int index, int size, int dpi) namespace
{
FT_Face
LoadFontFace(FT_Library ft, const fs::path &path, int index, int size, int dpi)
{ {
FT_Face face; FT_Face face;
@ -646,69 +708,89 @@ TextureFont* TextureFont::load(const Renderer *r, const fs::path &path, int inde
if (!FT_IS_SCALABLE(face)) if (!FT_IS_SCALABLE(face))
{ {
GetLogger()->error("Font is not scalable: {}\n", path); GetLogger()->error("Font is not scalable: {}\n", path);
FT_Done_Face(face);
return nullptr; return nullptr;
} }
if (FT_Set_Char_Size(face, 0, size << 6, dpi, dpi) != 0) if (FT_Set_Char_Size(face, 0, size << 6, dpi, dpi) != 0)
{ {
GetLogger()->error("Could not set font size {}\n", size); GetLogger()->error("Could not set font size {}\n", size);
FT_Done_Face(face);
return nullptr; return nullptr;
} }
auto* font = new TextureFont(r); return face;
font->impl->m_face = face;
if (!font->impl->buildAtlas())
return nullptr;
font->setMaxAscent(face->size->metrics.ascender >> 6);
font->setMaxDescent(-face->size->metrics.descender >> 6);
return font;
} }
// temporary while no fontconfig support // temporary while no fontconfig support
static fs::path ParseFontName(const fs::path &filename, int &collectionIndex, int &size) fs::path
ParseFontName(const fs::path &filename, int &index, int &size)
{ {
// Format with font path/collection index(if any)/font size(if any) // Format with font path/collection index(if any)/font size(if any)
auto fn = filename.string(); auto fn = filename.string();
auto pos = fn.rfind(','); if (auto ps = fn.rfind(','); ps != std::string::npos)
if (pos != string::npos)
{ {
size = (int) stof(fn.substr(pos + 1)); if (from_chars(&fn[ps + 1], &fn[fn.size()], size).ec == std::errc())
auto rest = fn.substr(0, pos);
pos = rest.rfind(',');
if (pos != string::npos)
{ {
collectionIndex = stof(rest.substr(pos + 1)); if (auto pi = fn.rfind(',', ps - 1); pi != std::string::npos)
return rest.substr(0, pos); {
} if (from_chars(&fn[pi + 1], &fn[pi], index).ec == std::errc())
else return fn.substr(0, pi);
{ }
return rest; return fn.substr(0, ps);
} }
} }
else return filename;
{
size = 12;
return filename;
}
} }
std::shared_ptr<TextureFont> LoadTextureFont(const Renderer *r, const fs::path &filename, int index, int size) } // namespace
using FontCache = std::map<fs::path, std::weak_ptr<TextureFont>>;
std::shared_ptr<TextureFont>
LoadTextureFont(const Renderer *r, const fs::path &filename, int index, int size)
{ {
if (ft == nullptr) // Init FreeType library
static FT_Library ftlib = nullptr;
if (ftlib == nullptr && FT_Init_FreeType(&ftlib) != 0)
{ {
if (FT_Init_FreeType(&ft)) GetLogger()->error("Could not init freetype library\n");
return nullptr;
}
// Init FontCache
static FontCache *fontCache = nullptr;
if (fontCache == nullptr)
fontCache = new FontCache;
// Lookup for an existing cached font
std::weak_ptr<TextureFont> &font = (*fontCache)[filename];
std::shared_ptr<TextureFont> ret = font.lock();
if (ret == nullptr)
{
int psize = 12; // default size if missing
int pindex = 0;
auto nameonly = ParseFontName(filename, pindex, psize);
auto face = LoadFontFace(ftlib, nameonly,
index > 0 ? index : pindex,
size > 0 ? size : psize,
r->getScreenDpi());
if (face == nullptr)
return nullptr;
ret = std::make_shared<TextureFont>(r);
ret->impl->m_face = face;
if (!ret->impl->buildAtlas())
{ {
GetLogger()->error("Could not init freetype library\n"); FT_Done_Face(face);
return nullptr; return nullptr;
} }
}
int psize = 0; ret->setMaxAscent(static_cast<int>(face->size->metrics.ascender >> 6));
int pcollectionIndex = 0; ret->setMaxDescent(static_cast<int>(-face->size->metrics.descender >> 6));
auto nameonly = ParseFontName(filename, pcollectionIndex, psize);
return std::shared_ptr<TextureFont>(TextureFont::load(r, nameonly, index > 0 ? index : pcollectionIndex, size > 0 ? size : psize, r->getScreenDpi())); font = ret;
}
return ret;
} }

View File

@ -1,6 +1,6 @@
// truetypefont.h // truetypefont.h
// //
// Copyright (C) 2019, Celestia Development Team // Copyright (C) 2019-2022, Celestia Development Team
// //
// This program is free software; you can redistribute it and/or // This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License // modify it under the terms of the GNU General Public License
@ -9,51 +9,53 @@
#pragma once #pragma once
#include <string>
#include <celcompat/filesystem.h>
#include <Eigen/Core> #include <Eigen/Core>
#include <celcompat/filesystem.h>
#include <string_view>
class Renderer; class Renderer;
class TextureFont;
std::shared_ptr<TextureFont>
LoadTextureFont(const Renderer *, const fs::path &, int index = 0, int size = 0);
struct TextureFontPrivate; struct TextureFontPrivate;
class TextureFont class TextureFont
{ {
TextureFont(const Renderer*);
public: public:
TextureFont(const Renderer *);
TextureFont() = delete; TextureFont() = delete;
~TextureFont(); ~TextureFont() = default;
TextureFont(const TextureFont&) = delete; TextureFont(const TextureFont &) = delete;
TextureFont(TextureFont&&) = delete; TextureFont(TextureFont &&) = delete;
TextureFont& operator=(const TextureFont&) = delete; TextureFont &operator=(const TextureFont &) = delete;
TextureFont& operator=(TextureFont&&) = delete; TextureFont &operator=(TextureFont &&) = delete;
void setMVPMatrices(const Eigen::Matrix4f& p, const Eigen::Matrix4f& m = Eigen::Matrix4f::Identity()); void setMVPMatrices(const Eigen::Matrix4f &p,
const Eigen::Matrix4f &m = Eigen::Matrix4f::Identity());
float render(wchar_t c, float xoffset = 0.0f, float yoffset = 0.0f) const; float render(wchar_t c, float xoffset = 0.0f, float yoffset = 0.0f) const;
float render(const std::string& str, float xoffset = 0.0f, float yoffset = 0.0f) const; float render(std::string_view str, float xoffset = 0.0f, float yoffset = 0.0f) const;
int getWidth(const std::string&) const; int getWidth(std::string_view) const;
int getWidth(int c) const; int getWidth(int c) const;
int getMaxWidth() const; int getMaxWidth() const;
int getHeight() const; int getHeight() const;
int getMaxAscent() const; int getMaxAscent() const;
void setMaxAscent(int); void setMaxAscent(int);
int getMaxDescent() const; int getMaxDescent() const;
void setMaxDescent(int); void setMaxDescent(int);
short getAdvance(wchar_t c) const; short getAdvance(wchar_t c) const;
int getTextureName() const;
void bind(); void bind();
void unbind(); void unbind();
bool buildTexture();
void flush(); void flush();
static TextureFont* load(const Renderer*, const fs::path&, int index, int size, int dpi);
private: private:
TextureFontPrivate *impl; std::unique_ptr<TextureFontPrivate> impl;
};
std::shared_ptr<TextureFont> LoadTextureFont(const Renderer*, const fs::path&, int index = 0, int size = 0); friend std::shared_ptr<TextureFont>
LoadTextureFont(const Renderer*, const fs::path&, int, int);
};