256 lines
8.2 KiB
C++
256 lines
8.2 KiB
C++
#include "pch.h"
|
|
#include "log.h"
|
|
#include "font.h"
|
|
#include "shader.h"
|
|
#include "asset.h"
|
|
#include "util.h"
|
|
#include "app.h"
|
|
|
|
std::map<kFont, Font> FontManager::m_fonts;
|
|
Sampler FontManager::m_sampler;
|
|
|
|
bool Font::load(const std::string& ttf, int font_size, float font_scale)
|
|
{
|
|
Asset file;
|
|
LOG("Font::load %s", ttf.c_str());
|
|
if (file.open(ttf.c_str()) && file.read_all())
|
|
{
|
|
w = h = 512 * (int)ceilf(font_scale);
|
|
path = ttf;
|
|
scale = font_scale;
|
|
LOG("Font::load loaded");
|
|
auto bitmap = std::make_unique<uint8_t[]>(w*h);
|
|
chars.resize(num_chars);
|
|
stbtt_BakeFontBitmap(file.m_data, 0, (float)font_size*scale, bitmap.get(), w, h, start_char, num_chars, chars.data());
|
|
calc_bounds();
|
|
font_tex.create(w, h, GL_R8, GL_RED, bitmap.get());
|
|
file.close();
|
|
size = font_size;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Font::change_scale(float scale)
|
|
{
|
|
load(path, size, scale);
|
|
}
|
|
|
|
void Font::calc_bounds()
|
|
{
|
|
glm::vec2 bbmin(FLT_MAX);
|
|
glm::vec2 bbmax(-FLT_MAX);
|
|
for (int i = 0; i < num_chars; i++)
|
|
{
|
|
stbtt_aligned_quad q;
|
|
float x = 0, y = 0;
|
|
stbtt_GetBakedQuad(chars.data(), w, h, i, &x, &y, &q, true);
|
|
bbmin = glm::min(bbmin, { q.x0 / scale, q.y0 / scale });
|
|
bbmax = glm::max(bbmax, { q.x1 / scale, q.y1 / scale });
|
|
}
|
|
bounds = { glm::vec4(bbmin, bbmax) };
|
|
}
|
|
|
|
void FontManager::init()
|
|
{
|
|
m_sampler.create();
|
|
}
|
|
|
|
bool FontManager::load(kFont id, const char* ttf, int sz, float scale)
|
|
{
|
|
return m_fonts[id].load(ttf, sz, scale);
|
|
}
|
|
|
|
const Font& FontManager::get(kFont id)
|
|
{
|
|
return m_fonts[id];
|
|
}
|
|
|
|
void FontManager::change_scale(float scale)
|
|
{
|
|
for (auto& f : m_fonts)
|
|
f.second.change_scale(scale);
|
|
}
|
|
|
|
std::vector<TextMesh::Token> TextMesh::tokenize(const std::string& s, const Font& f) const noexcept
|
|
{
|
|
std::vector<std::string> parts;
|
|
std::array<char, 10> delims = { ' ', '\n', ',', '.', ':', ';', '?', '!', '\\', '/' };
|
|
std::string tmp;
|
|
bool wrap = false;
|
|
for (char c : s)
|
|
{
|
|
bool is_delim = std::find(delims.begin(), delims.end(), c) != delims.end();
|
|
wrap |= is_delim; // set wrap to notify a delimiter has been reached
|
|
// when a new non-delim char is detected, start a new token
|
|
if (wrap && !is_delim && !tmp.empty())
|
|
{
|
|
parts.push_back(tmp);
|
|
tmp.clear();
|
|
wrap = false;
|
|
}
|
|
tmp.push_back(c);
|
|
}
|
|
// insert last partial token
|
|
if (!tmp.empty())
|
|
parts.push_back(tmp);
|
|
// measure each token length
|
|
std::vector<TextMesh::Token> ret;
|
|
for (auto p : parts)
|
|
{
|
|
float x = 0;
|
|
float y = 0;
|
|
for (char c : p)
|
|
{
|
|
if ((uint8_t)c >= 32 && (uint8_t)c < 256) // visible ascii character
|
|
{
|
|
stbtt_aligned_quad q;
|
|
stbtt_GetBakedQuad((stbtt_bakedchar*)f.chars.data(), f.w, f.h, (uint8_t)c - f.start_char, &x, &y, &q, true);
|
|
}
|
|
}
|
|
ret.emplace_back(p, x);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool TextMesh::create()
|
|
{
|
|
App::I->render_task([this]
|
|
{
|
|
glGenBuffers(2, font_buffers);
|
|
#if USE_VBO
|
|
glGenVertexArrays(1, &font_array);
|
|
glBindVertexArray(font_array);
|
|
glEnableVertexAttribArray(0);
|
|
glEnableVertexAttribArray(1);
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, font_buffers[1]);
|
|
glBindBuffer(GL_ARRAY_BUFFER, font_buffers[0]);
|
|
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(glm::vec4), (GLvoid*)0);
|
|
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(glm::vec4), (GLvoid*)(sizeof(float) * 2));
|
|
glBindVertexArray(0);
|
|
#endif // USE_VBO
|
|
});
|
|
return true;
|
|
}
|
|
|
|
void TextMesh::update(kFont id, const std::string& text)
|
|
{
|
|
font_id = id;
|
|
auto& f = FontManager::get(id);
|
|
float spacing = f.bounds.w - f.bounds.y;
|
|
float avg_width = f.bounds.z - f.bounds.x;
|
|
cur_box = glm::vec4(0, f.bounds.y, 5, spacing);
|
|
|
|
glm::vec2 bbmin(FLT_MAX);
|
|
glm::vec2 bbmax(-FLT_MAX);
|
|
if (text.empty())
|
|
{
|
|
bbmin = { 0, -spacing * 0.5f };
|
|
bbmax = { 1, +spacing * 0.5f };
|
|
}
|
|
|
|
if (f.chars.size())
|
|
{
|
|
float x = 0;
|
|
float y = 0;
|
|
std::vector<glm::vec4> v;
|
|
std::vector<GLushort> idx;
|
|
|
|
std::vector<Token> parts = max_width > 0 ?
|
|
tokenize(text, f) :
|
|
std::vector<Token>{ Token(text, 0.f) };
|
|
|
|
for (size_t i = 0; i < parts.size(); i++)
|
|
{
|
|
const auto& p = parts[i];
|
|
if (i > 0 && max_width > 0 && (x + p.w) > max_width * f.scale)
|
|
{
|
|
x = 0;
|
|
y += spacing * f.scale;
|
|
}
|
|
for (char c : p.s)
|
|
{
|
|
if (c == '\n')
|
|
{
|
|
x = 0;
|
|
y += spacing * f.scale;
|
|
cur_box = glm::vec4(x, y / f.scale + f.bounds.y, 5, spacing);
|
|
continue;
|
|
}
|
|
stbtt_aligned_quad q;
|
|
float wrap_test_x = x;
|
|
stbtt_GetBakedQuad((stbtt_bakedchar*)f.chars.data(), f.w, f.h, (uint8_t)c - f.start_char, &wrap_test_x, &y, &q, true);
|
|
if (max_width > 0 && q.x1 > max_width * f.scale)
|
|
{
|
|
x = 0;
|
|
y += spacing * f.scale;
|
|
}
|
|
stbtt_GetBakedQuad((stbtt_bakedchar*)f.chars.data(), f.w, f.h, (uint8_t)c - f.start_char, &x, &y, &q, true);
|
|
auto n = (int)v.size();
|
|
v.emplace_back(q.x0 / f.scale, q.y1 / f.scale, q.s0, q.t1);
|
|
v.emplace_back(q.x0 / f.scale, q.y0 / f.scale, q.s0, q.t0);
|
|
v.emplace_back(q.x1 / f.scale, q.y0 / f.scale, q.s1, q.t0);
|
|
v.emplace_back(q.x1 / f.scale, q.y1 / f.scale, q.s1, q.t1);
|
|
idx.push_back(n + 0);
|
|
idx.push_back(n + 1);
|
|
idx.push_back(n + 2);
|
|
idx.push_back(n + 0);
|
|
idx.push_back(n + 2);
|
|
idx.push_back(n + 3);
|
|
bbmin = glm::min(bbmin, xy(f.bounds));
|
|
bbmax = glm::max(bbmax, { q.x1 / f.scale, y + f.bounds.w });
|
|
if (max_width > 0 && q.x1 / f.scale + 5 > max_width * f.scale)
|
|
cur_box = glm::vec4(0, (y + spacing * f.scale) / f.scale + f.bounds.y, 5, spacing);
|
|
else
|
|
cur_box = glm::vec4(q.x1 / f.scale + 2, y / f.scale + f.bounds.y, 5, spacing);
|
|
}
|
|
}
|
|
for (auto& vi : v)
|
|
vi -= glm::vec4(xy(f.bounds), 0, 0);
|
|
cur_box -= glm::vec4(xy(f.bounds), 0, 0);
|
|
bb = bbmax - bbmin;
|
|
font_array_count = (int)idx.size();
|
|
App::I->render_task([&]
|
|
{
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, font_buffers[1]);
|
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx.size() * sizeof(GLushort), idx.data(), GL_STATIC_DRAW);
|
|
glBindBuffer(GL_ARRAY_BUFFER, font_buffers[0]);
|
|
glBufferData(GL_ARRAY_BUFFER, v.size() * sizeof(glm::vec4), v.data(), GL_STATIC_DRAW);
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
});
|
|
}
|
|
}
|
|
|
|
void TextMesh::draw()
|
|
{
|
|
auto& f = FontManager::get(font_id);
|
|
if (f.font_tex.ready())
|
|
{
|
|
glActiveTexture(GL_TEXTURE0);
|
|
f.font_tex.bind();
|
|
FontManager::m_sampler.bind(0);
|
|
|
|
#if USE_VBO
|
|
glBindVertexArray(font_array);
|
|
glDrawElements(GL_TRIANGLES, font_array_count, GL_UNSIGNED_SHORT, 0);
|
|
glBindVertexArray(0);
|
|
#else
|
|
glEnableVertexAttribArray(0);
|
|
glEnableVertexAttribArray(1);
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, font_buffers[1]);
|
|
glBindBuffer(GL_ARRAY_BUFFER, font_buffers[0]);
|
|
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(glm::vec4), (GLvoid*)0);
|
|
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(glm::vec4), (GLvoid*)(sizeof(float) * 2));
|
|
glDrawElements(GL_TRIANGLES, font_array_count, GL_UNSIGNED_SHORT, 0);
|
|
glDisableVertexAttribArray(0);
|
|
glDisableVertexAttribArray(1);
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
#endif // USE_VBO
|
|
|
|
f.font_tex.unbind();
|
|
FontManager::m_sampler.unbind();
|
|
}
|
|
}
|