Browse Source

Engine: Fix font spacing in TextAreaComponent

master
Riyyi 4 months ago
parent
commit
574f2a8b5a
  1. 2
      src/inferno/component/textareacomponent.cpp
  2. 98
      src/inferno/render/font.cpp
  3. 30
      src/inferno/render/font.h
  4. 63
      src/inferno/system/textareasystem.cpp
  5. 2
      src/inferno/system/textareasystem.h

2
src/inferno/component/textareacomponent.cpp

@ -27,7 +27,7 @@ void fromJson(const ruc::Json& json, TextAreaComponent& value)
json.at("width").getTo(value.width);
}
if (json.exists("lines") && json.at("lines").type() == ruc::Json::Type::Number) {
json.at("lines").getTo(value.width);
json.at("lines").getTo(value.lines);
}
}

98
src/inferno/render/font.cpp

@ -4,13 +4,18 @@
* SPDX-License-Identifier: MIT
*/
#include <limits> // std::numeric_limits
#include <string> // std::getline, std::stoi
#include <utility> // std::move
#include <array> // std::array
#include <charconv> // std;:from_chars
#include <cstdint> // int32_t, uint32_t
#include <limits> // std::numeric_limits
#include <ranges> // std::views::split
#include <string> // std::getline
#include <utility> // std::move
#include "ruc/file.h"
#include "ruc/format/log.h"
#include "ruc/meta/assert.h"
#include "ruc/meta/concepts.h"
#include "inferno/render/font.h"
#include "inferno/render/texture.h"
@ -30,43 +35,96 @@ Font::Font(const std::string& name)
m_texture = std::make_shared<Texture>(image);
}
// TODO: Move this to ruc
template<Integral T>
static T convert(std::string_view value)
{
T tmp;
std::from_chars(value.data(), value.data() + value.size(), tmp);
return tmp;
}
void Font::parseFont(const std::string& font)
{
std::istringstream iss(font);
for (std::string line; std::getline(iss, line);) {
std::string action = findAction(line);
const std::vector<std::string> columns = findColumns(line);
if (findAction(line).compare("info") != 0 && findAction(line).compare("char") != 0) {
// Info
// ---------------------------------
if (action.compare("info") == 0) {
m_size = convert<uint32_t>(findValue("size", columns));
auto paddings = findValue("padding", columns) | std::views::split(',');
size_t i = 0;
for (const auto& padding : paddings) {
// top, right, bottom, left
m_padding[i++] = convert<uint32_t>(padding.data()) - PADDING;
}
continue;
}
const std::vector<std::string> columns = findColumns(line);
// Info
// Common
// ---------------------------------
if (findAction(line).compare("info") == 0) {
m_size = std::stou(findValue("size", columns));
if (action.compare("common") == 0) {
m_lineSpacing = convert<uint32_t>(findValue("lineHeight", columns)) - m_padding[Padding::Top] - m_padding[Padding::Bottom];
continue;
}
// Character
// ---------------------------------
if (action.compare("char") == 0) {
unsigned char id = convert<unsigned char>(findValue("id", columns));
uint32_t width = convert<uint32_t>(findValue("width", columns));
uint32_t height = convert<uint32_t>(findValue("height", columns));
Character character = {
.position = {
convert<uint32_t>(findValue("x", columns)) + m_padding[Padding::Left],
convert<uint32_t>(findValue("y", columns)) + m_padding[Padding::Top],
},
.size = {
width == 0 ? 0 : width - m_padding[Padding::Left] - m_padding[Padding::Right],
height == 0 ? 0 : height - m_padding[Padding::Top] - m_padding[Padding::Bottom],
},
.offset = {
convert<int32_t>(findValue("xoffset", columns)) + m_padding[Padding::Left],
convert<int32_t>(findValue("yoffset", columns)) + m_padding[Padding::Top],
},
.advance = convert<uint32_t>(findValue("xadvance", columns)) - m_padding[Padding::Left] - m_padding[Padding::Right]
};
m_characterList.emplace(id, std::make_shared<Character>(character));
continue;
}
// Kerning
// ---------------------------------
if (action.compare("kerning") == 0) {
unsigned char first = convert<unsigned char>(findValue("first", columns));
unsigned char second = convert<unsigned char>(findValue("second", columns));
char amount = convert<char>(findValue("amount", columns));
unsigned char id = std::stoi(findValue("id", columns));
Character character = {
{ std::stou(findValue("x", columns)), std::stou(findValue("y", columns)) },
{ std::stou(findValue("width", columns)), std::stou(findValue("height", columns)) },
{ std::stoi(findValue("xoffset", columns)), std::stoi(findValue("yoffset", columns)) },
std::stou(findValue("xadvance", columns))
};
m_characterList.emplace(id, std::make_shared<Character>(character));
// Add the kerning of the previous character to this character
if (m_characterList.find(second) != m_characterList.end()) {
auto character = m_characterList.at(second);
character->kernings.emplace(first, amount);
}
continue;
}
}
}
const std::string Font::findAction(const std::string& line)
std::string Font::findAction(const std::string& line) const
{
return line.substr(0, line.find(" "));
}
const std::vector<std::string> Font::findColumns(const std::string& line)
std::vector<std::string> Font::findColumns(const std::string& line) const
{
std::vector<std::string> elements;
@ -93,7 +151,7 @@ const std::vector<std::string> Font::findColumns(const std::string& line)
return elements;
}
const std::string Font::findValue(const std::string& key, const std::vector<std::string>& columns)
std::string Font::findValue(const std::string& key, const std::vector<std::string>& columns) const
{
size_t find = 0;
// Loop over columns

30
src/inferno/render/font.h

@ -6,6 +6,7 @@
#pragma once
#include <array> // std::array
#include <cstdint> // int32_t, uint32_t
#include <memory> // std::shared_ptr
#include <string> // std::string
@ -17,15 +18,18 @@
#include "ruc/format/format.h"
#include "ruc/singleton.h"
#define PADDING 3
namespace Inferno {
class Texture;
struct Character {
glm::uvec2 position; // Position
glm::uvec2 size; // Width/height
glm::ivec2 offset; // Offset from baseline to left / top of glyph
uint32_t advance; // Amount to advance to next glyph
glm::uvec2 position; // Position
glm::uvec2 size; // Width/height
glm::ivec2 offset; // Offset from baseline to left / top of glyph
uint32_t advance; // Amount to advance to next glyph
std::unordered_map<unsigned char, char> kernings; // Kernings for characters that come before this one
};
// -------------------------------------
@ -35,8 +39,16 @@ public:
Font(const std::string& name);
virtual ~Font() {}
enum Padding {
Top = 0,
Right,
Bottom,
Left,
};
inline std::string name() const { return m_name; }
inline uint32_t size() const { return m_size; }
inline uint32_t lineSpacing() const { return m_lineSpacing; }
inline std::shared_ptr<Texture> texture() const { return m_texture; }
inline std::shared_ptr<Character> get(unsigned char c) const { return m_characterList.at(c); }
@ -44,12 +56,14 @@ public:
private:
void parseFont(const std::string& font);
const std::string findAction(const std::string& line);
const std::vector<std::string> findColumns(const std::string& line);
const std::string findValue(const std::string& key, const std::vector<std::string>& columns);
std::string findAction(const std::string& line) const;
std::vector<std::string> findColumns(const std::string& line) const;
std::string findValue(const std::string& key, const std::vector<std::string>& columns) const;
std::string m_name;
uint32_t m_size;
uint32_t m_size = { 0 };
uint32_t m_lineSpacing = { 0 };
std::array<uint32_t, 4> m_padding = { 0 };
std::shared_ptr<Texture> m_texture;
std::unordered_map<unsigned char, std::shared_ptr<Character>> m_characterList;
};

63
src/inferno/system/textareasystem.cpp

@ -4,6 +4,8 @@
* SPDX-License-Identifier: MIT
*/
#include <algorithm> // std::max
#include "ruc/format/log.h"
#include "ruc/meta/assert.h"
@ -46,9 +48,12 @@ void TextAreaSystem::render()
std::shared_ptr<Font> font = FontManager::the().load(textarea.font);
// glm::mat4 translate = transform.translate;
float advance = 0.0f;
unsigned char previous = 0;
float advanceX = 0.0f;
float advanceY = 0.0f;
for (auto character : textarea.content) {
std::optional<CharacterQuad> quad = calculateCharacterQuad(character, font, advance);
std::optional<CharacterQuad> quad = calculateCharacterQuad(character, previous, font, advanceX, advanceY);
previous = character;
if (quad) {
RendererCharacter::the().drawCharacter(quad.value(), font->texture());
@ -57,53 +62,65 @@ void TextAreaSystem::render()
}
}
std::optional<CharacterQuad> TextAreaSystem::calculateCharacterQuad(unsigned char character, std::shared_ptr<Font> font, float& advance)
std::optional<CharacterQuad> TextAreaSystem::calculateCharacterQuad(unsigned char character, unsigned char previous, std::shared_ptr<Font> font, float& advanceX, float& advanceY)
{
CharacterQuad characterQuad;
auto c = font->get(character);
// Texture
// ---------------------------------
// -------------------------------------
float textureWidth = static_cast<float>(font->texture()->width());
float textureHeight = static_cast<float>(font->texture()->height());
VERIFY(textureWidth == textureHeight, "TextAreaSystem read invalid font texture");
// Skip empty characters
// Skip empty characters (like space)
if (c->size.x == 0 || c->size.y == 0) {
// Jump to the next glyph
advance += c->advance / textureWidth;
advanceX += c->advance;
return {};
}
// Position
// ---------------------------------
// -------------------------------------
float quadWidth = c->size.x / textureWidth;
float quadHeight = c->size.y / textureHeight;
characterQuad.at(0).quad.position = { -quadWidth, -quadHeight, 0.0f }; // bottom left
characterQuad.at(1).quad.position = { quadWidth, -quadHeight, 0.0f }; // bottom right
characterQuad.at(2).quad.position = { quadWidth, quadHeight, 0.0f }; // top right
characterQuad.at(3).quad.position = { -quadWidth, quadHeight, 0.0f }; // top left
// Kerning
if (c->kernings.find(previous) != c->kernings.end()) {
advanceX += c->kernings.at(previous);
}
for (auto& quad : characterQuad) {
quad.quad.position.x -= 0.5f;
// Line wrapping
if (advanceX + c->offset.x + c->size.x > textureWidth) {
advanceX = 0;
advanceY -= font->lineSpacing();
}
quad.quad.position.x += c->offset.x / (float)textureWidth;
quad.quad.position.y -= c->offset.y / (float)textureHeight;
glm::vec2 cursor = { std::max(advanceX + c->offset.x, 0.0f),
advanceY - c->offset.y };
glm::vec2 cursorMax = { cursor.x + c->size.x,
cursor.y - c->size.y };
quad.quad.position.x += advance;
}
// Scale the values from 0:512 (texture size) to -1:1 (screen space)
glm::vec2 cursorScreen = {
(cursor.x / textureWidth * 2) - 1,
(cursor.y / textureHeight * 2) + 1,
};
glm::vec2 cursorScreenMax = {
(cursorMax.x / textureWidth * 2) - 1,
(cursorMax.y / textureHeight * 2) + 1,
};
// ruc::debug("character: {} ({}) width: {} height: {} advance: {} x: {}",
// character, (int)character, quadWidth, quadHeight, advance, characterQuad.at(0).quad.position.x);
characterQuad.at(0).quad.position = { cursorScreen.x, cursorScreenMax.y, 0.0f }; // bottom left
characterQuad.at(1).quad.position = { cursorScreenMax.x, cursorScreenMax.y, 0.0f }; // bottom right
characterQuad.at(2).quad.position = { cursorScreenMax.x, cursorScreen.y, 0.0f }; // top right
characterQuad.at(3).quad.position = { cursorScreen.x, cursorScreen.y, 0.0f }; // top left
// Jump to the next glyph
advance += c->advance / textureWidth;
advanceX += c->advance;
// Texture coordinates
// ---------------------------------
// -------------------------------------
glm::vec2 x {
1 - (textureWidth - c->position.x) / textureWidth,

2
src/inferno/system/textareasystem.h

@ -33,7 +33,7 @@ public:
void setScene(Scene* scene) { m_scene = scene; }
private:
std::optional<CharacterQuad> calculateCharacterQuad(unsigned char character, std::shared_ptr<Font> font, float& advance);
std::optional<CharacterQuad> calculateCharacterQuad(unsigned char character, unsigned char previous, std::shared_ptr<Font> font, float& advanceX, float& advanceY);
Scene* m_scene { nullptr };
};

Loading…
Cancel
Save