Engine: Fix font spacing in TextAreaComponent
This commit is contained in:
@@ -27,7 +27,7 @@ void fromJson(const ruc::Json& json, TextAreaComponent& value)
|
|||||||
json.at("width").getTo(value.width);
|
json.at("width").getTo(value.width);
|
||||||
}
|
}
|
||||||
if (json.exists("lines") && json.at("lines").type() == ruc::Json::Type::Number) {
|
if (json.exists("lines") && json.at("lines").type() == ruc::Json::Type::Number) {
|
||||||
json.at("lines").getTo(value.width);
|
json.at("lines").getTo(value.lines);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+79
-21
@@ -4,13 +4,18 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <limits> // std::numeric_limits
|
#include <array> // std::array
|
||||||
#include <string> // std::getline, std::stoi
|
#include <charconv> // std;:from_chars
|
||||||
#include <utility> // std::move
|
#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/file.h"
|
||||||
#include "ruc/format/log.h"
|
#include "ruc/format/log.h"
|
||||||
#include "ruc/meta/assert.h"
|
#include "ruc/meta/assert.h"
|
||||||
|
#include "ruc/meta/concepts.h"
|
||||||
|
|
||||||
#include "inferno/render/font.h"
|
#include "inferno/render/font.h"
|
||||||
#include "inferno/render/texture.h"
|
#include "inferno/render/texture.h"
|
||||||
@@ -30,43 +35,96 @@ Font::Font(const std::string& name)
|
|||||||
m_texture = std::make_shared<Texture>(image);
|
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)
|
void Font::parseFont(const std::string& font)
|
||||||
{
|
{
|
||||||
std::istringstream iss(font);
|
std::istringstream iss(font);
|
||||||
for (std::string line; std::getline(iss, line);) {
|
for (std::string line; std::getline(iss, line);) {
|
||||||
|
std::string action = findAction(line);
|
||||||
if (findAction(line).compare("info") != 0 && findAction(line).compare("char") != 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::vector<std::string> columns = findColumns(line);
|
const std::vector<std::string> columns = findColumns(line);
|
||||||
|
|
||||||
// Info
|
// Info
|
||||||
|
// ---------------------------------
|
||||||
|
|
||||||
if (findAction(line).compare("info") == 0) {
|
if (action.compare("info") == 0) {
|
||||||
m_size = std::stou(findValue("size", columns));
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common
|
||||||
|
// ---------------------------------
|
||||||
|
|
||||||
|
if (action.compare("common") == 0) {
|
||||||
|
m_lineSpacing = convert<uint32_t>(findValue("lineHeight", columns)) - m_padding[Padding::Top] - m_padding[Padding::Bottom];
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Character
|
// Character
|
||||||
|
// ---------------------------------
|
||||||
|
|
||||||
unsigned char id = std::stoi(findValue("id", columns));
|
if (action.compare("char") == 0) {
|
||||||
Character character = {
|
unsigned char id = convert<unsigned char>(findValue("id", columns));
|
||||||
{ std::stou(findValue("x", columns)), std::stou(findValue("y", columns)) },
|
uint32_t width = convert<uint32_t>(findValue("width", columns));
|
||||||
{ std::stou(findValue("width", columns)), std::stou(findValue("height", columns)) },
|
uint32_t height = convert<uint32_t>(findValue("height", columns));
|
||||||
{ std::stoi(findValue("xoffset", columns)), std::stoi(findValue("yoffset", columns)) },
|
Character character = {
|
||||||
std::stou(findValue("xadvance", columns))
|
.position = {
|
||||||
};
|
convert<uint32_t>(findValue("x", columns)) + m_padding[Padding::Left],
|
||||||
m_characterList.emplace(id, std::make_shared<Character>(character));
|
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));
|
||||||
|
|
||||||
|
// 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(" "));
|
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;
|
std::vector<std::string> elements;
|
||||||
|
|
||||||
@@ -93,7 +151,7 @@ const std::vector<std::string> Font::findColumns(const std::string& line)
|
|||||||
return elements;
|
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;
|
size_t find = 0;
|
||||||
// Loop over columns
|
// Loop over columns
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <array> // std::array
|
||||||
#include <cstdint> // int32_t, uint32_t
|
#include <cstdint> // int32_t, uint32_t
|
||||||
#include <memory> // std::shared_ptr
|
#include <memory> // std::shared_ptr
|
||||||
#include <string> // std::string
|
#include <string> // std::string
|
||||||
@@ -17,15 +18,18 @@
|
|||||||
#include "ruc/format/format.h"
|
#include "ruc/format/format.h"
|
||||||
#include "ruc/singleton.h"
|
#include "ruc/singleton.h"
|
||||||
|
|
||||||
|
#define PADDING 3
|
||||||
|
|
||||||
namespace Inferno {
|
namespace Inferno {
|
||||||
|
|
||||||
class Texture;
|
class Texture;
|
||||||
|
|
||||||
struct Character {
|
struct Character {
|
||||||
glm::uvec2 position; // Position
|
glm::uvec2 position; // Position
|
||||||
glm::uvec2 size; // Width/height
|
glm::uvec2 size; // Width/height
|
||||||
glm::ivec2 offset; // Offset from baseline to left / top of glyph
|
glm::ivec2 offset; // Offset from baseline to left / top of glyph
|
||||||
uint32_t advance; // Amount to advance to next 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);
|
Font(const std::string& name);
|
||||||
virtual ~Font() {}
|
virtual ~Font() {}
|
||||||
|
|
||||||
|
enum Padding {
|
||||||
|
Top = 0,
|
||||||
|
Right,
|
||||||
|
Bottom,
|
||||||
|
Left,
|
||||||
|
};
|
||||||
|
|
||||||
inline std::string name() const { return m_name; }
|
inline std::string name() const { return m_name; }
|
||||||
inline uint32_t size() const { return m_size; }
|
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<Texture> texture() const { return m_texture; }
|
||||||
|
|
||||||
inline std::shared_ptr<Character> get(unsigned char c) const { return m_characterList.at(c); }
|
inline std::shared_ptr<Character> get(unsigned char c) const { return m_characterList.at(c); }
|
||||||
@@ -44,12 +56,14 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void parseFont(const std::string& font);
|
void parseFont(const std::string& font);
|
||||||
const std::string findAction(const std::string& line);
|
std::string findAction(const std::string& line) const;
|
||||||
const std::vector<std::string> findColumns(const std::string& line);
|
std::vector<std::string> findColumns(const std::string& line) const;
|
||||||
const std::string findValue(const std::string& key, const std::vector<std::string>& columns);
|
std::string findValue(const std::string& key, const std::vector<std::string>& columns) const;
|
||||||
|
|
||||||
std::string m_name;
|
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::shared_ptr<Texture> m_texture;
|
||||||
std::unordered_map<unsigned char, std::shared_ptr<Character>> m_characterList;
|
std::unordered_map<unsigned char, std::shared_ptr<Character>> m_characterList;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <algorithm> // std::max
|
||||||
|
|
||||||
#include "ruc/format/log.h"
|
#include "ruc/format/log.h"
|
||||||
#include "ruc/meta/assert.h"
|
#include "ruc/meta/assert.h"
|
||||||
|
|
||||||
@@ -46,9 +48,12 @@ void TextAreaSystem::render()
|
|||||||
std::shared_ptr<Font> font = FontManager::the().load(textarea.font);
|
std::shared_ptr<Font> font = FontManager::the().load(textarea.font);
|
||||||
// glm::mat4 translate = transform.translate;
|
// 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) {
|
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) {
|
if (quad) {
|
||||||
RendererCharacter::the().drawCharacter(quad.value(), font->texture());
|
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;
|
CharacterQuad characterQuad;
|
||||||
|
|
||||||
auto c = font->get(character);
|
auto c = font->get(character);
|
||||||
|
|
||||||
// Texture
|
// Texture
|
||||||
// ---------------------------------
|
// -------------------------------------
|
||||||
|
|
||||||
float textureWidth = static_cast<float>(font->texture()->width());
|
float textureWidth = static_cast<float>(font->texture()->width());
|
||||||
float textureHeight = static_cast<float>(font->texture()->height());
|
float textureHeight = static_cast<float>(font->texture()->height());
|
||||||
VERIFY(textureWidth == textureHeight, "TextAreaSystem read invalid font texture");
|
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) {
|
if (c->size.x == 0 || c->size.y == 0) {
|
||||||
// Jump to the next glyph
|
// Jump to the next glyph
|
||||||
advance += c->advance / textureWidth;
|
advanceX += c->advance;
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Position
|
// Position
|
||||||
// ---------------------------------
|
// -------------------------------------
|
||||||
|
|
||||||
float quadWidth = c->size.x / textureWidth;
|
// Kerning
|
||||||
float quadHeight = c->size.y / textureHeight;
|
if (c->kernings.find(previous) != c->kernings.end()) {
|
||||||
characterQuad.at(0).quad.position = { -quadWidth, -quadHeight, 0.0f }; // bottom left
|
advanceX += c->kernings.at(previous);
|
||||||
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
|
|
||||||
|
|
||||||
for (auto& quad : characterQuad) {
|
|
||||||
quad.quad.position.x -= 0.5f;
|
|
||||||
|
|
||||||
quad.quad.position.x += c->offset.x / (float)textureWidth;
|
|
||||||
quad.quad.position.y -= c->offset.y / (float)textureHeight;
|
|
||||||
|
|
||||||
quad.quad.position.x += advance;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ruc::debug("character: {} ({}) width: {} height: {} advance: {} x: {}",
|
// Line wrapping
|
||||||
// character, (int)character, quadWidth, quadHeight, advance, characterQuad.at(0).quad.position.x);
|
if (advanceX + c->offset.x + c->size.x > textureWidth) {
|
||||||
|
advanceX = 0;
|
||||||
|
advanceY -= font->lineSpacing();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 };
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
// Jump to the next glyph
|
||||||
advance += c->advance / textureWidth;
|
advanceX += c->advance;
|
||||||
|
|
||||||
// Texture coordinates
|
// Texture coordinates
|
||||||
// ---------------------------------
|
// -------------------------------------
|
||||||
|
|
||||||
glm::vec2 x {
|
glm::vec2 x {
|
||||||
1 - (textureWidth - c->position.x) / textureWidth,
|
1 - (textureWidth - c->position.x) / textureWidth,
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ public:
|
|||||||
void setScene(Scene* scene) { m_scene = scene; }
|
void setScene(Scene* scene) { m_scene = scene; }
|
||||||
|
|
||||||
private:
|
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 };
|
Scene* m_scene { nullptr };
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user