From 574f2a8b5a849ae0bc265ef55323c3ed8d97ac5e Mon Sep 17 00:00:00 2001 From: Riyyi Date: Tue, 26 Dec 2023 01:31:12 +0100 Subject: [PATCH] Engine: Fix font spacing in TextAreaComponent --- src/inferno/component/textareacomponent.cpp | 2 +- src/inferno/render/font.cpp | 98 ++++++++++++++++----- src/inferno/render/font.h | 30 +++++-- src/inferno/system/textareasystem.cpp | 63 ++++++++----- src/inferno/system/textareasystem.h | 2 +- 5 files changed, 142 insertions(+), 53 deletions(-) diff --git a/src/inferno/component/textareacomponent.cpp b/src/inferno/component/textareacomponent.cpp index c97599c..1516ae6 100644 --- a/src/inferno/component/textareacomponent.cpp +++ b/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); } } diff --git a/src/inferno/render/font.cpp b/src/inferno/render/font.cpp index 3e5838b..264800e 100644 --- a/src/inferno/render/font.cpp +++ b/src/inferno/render/font.cpp @@ -4,13 +4,18 @@ * SPDX-License-Identifier: MIT */ -#include // std::numeric_limits -#include // std::getline, std::stoi -#include // std::move +#include // std::array +#include // std;:from_chars +#include // int32_t, uint32_t +#include // std::numeric_limits +#include // std::views::split +#include // std::getline +#include // 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(image); } +// TODO: Move this to ruc +template +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 columns = findColumns(line); - if (findAction(line).compare("info") != 0 && findAction(line).compare("char") != 0) { + // Info + // --------------------------------- + + if (action.compare("info") == 0) { + m_size = convert(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(padding.data()) - PADDING; + } continue; } - const std::vector 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(findValue("lineHeight", columns)) - m_padding[Padding::Top] - m_padding[Padding::Bottom]; continue; } // Character + // --------------------------------- + + if (action.compare("char") == 0) { + unsigned char id = convert(findValue("id", columns)); + uint32_t width = convert(findValue("width", columns)); + uint32_t height = convert(findValue("height", columns)); + Character character = { + .position = { + convert(findValue("x", columns)) + m_padding[Padding::Left], + convert(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(findValue("xoffset", columns)) + m_padding[Padding::Left], + convert(findValue("yoffset", columns)) + m_padding[Padding::Top], + }, + .advance = convert(findValue("xadvance", columns)) - m_padding[Padding::Left] - m_padding[Padding::Right] + }; + + m_characterList.emplace(id, std::make_shared(character)); + continue; + } + + // Kerning + // --------------------------------- + + if (action.compare("kerning") == 0) { + unsigned char first = convert(findValue("first", columns)); + unsigned char second = convert(findValue("second", columns)); + char amount = convert(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)); + // 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 Font::findColumns(const std::string& line) +std::vector Font::findColumns(const std::string& line) const { std::vector elements; @@ -93,7 +151,7 @@ const std::vector Font::findColumns(const std::string& line) return elements; } -const std::string Font::findValue(const std::string& key, const std::vector& columns) +std::string Font::findValue(const std::string& key, const std::vector& columns) const { size_t find = 0; // Loop over columns diff --git a/src/inferno/render/font.h b/src/inferno/render/font.h index d719d7e..41e0cf6 100644 --- a/src/inferno/render/font.h +++ b/src/inferno/render/font.h @@ -6,6 +6,7 @@ #pragma once +#include // std::array #include // int32_t, uint32_t #include // std::shared_ptr #include // 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 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() const { return m_texture; } inline std::shared_ptr 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 findColumns(const std::string& line); - const std::string findValue(const std::string& key, const std::vector& columns); + std::string findAction(const std::string& line) const; + std::vector findColumns(const std::string& line) const; + std::string findValue(const std::string& key, const std::vector& columns) const; std::string m_name; - uint32_t m_size; + uint32_t m_size = { 0 }; + uint32_t m_lineSpacing = { 0 }; + std::array m_padding = { 0 }; std::shared_ptr m_texture; std::unordered_map> m_characterList; }; diff --git a/src/inferno/system/textareasystem.cpp b/src/inferno/system/textareasystem.cpp index 90bd766..4d3e71f 100644 --- a/src/inferno/system/textareasystem.cpp +++ b/src/inferno/system/textareasystem.cpp @@ -4,6 +4,8 @@ * SPDX-License-Identifier: MIT */ +#include // std::max + #include "ruc/format/log.h" #include "ruc/meta/assert.h" @@ -46,9 +48,12 @@ void TextAreaSystem::render() std::shared_ptr 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 quad = calculateCharacterQuad(character, font, advance); + std::optional 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 TextAreaSystem::calculateCharacterQuad(unsigned char character, std::shared_ptr font, float& advance) +std::optional TextAreaSystem::calculateCharacterQuad(unsigned char character, unsigned char previous, std::shared_ptr font, float& advanceX, float& advanceY) { CharacterQuad characterQuad; auto c = font->get(character); // Texture - // --------------------------------- + // ------------------------------------- float textureWidth = static_cast(font->texture()->width()); float textureHeight = static_cast(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, diff --git a/src/inferno/system/textareasystem.h b/src/inferno/system/textareasystem.h index 21fe363..c1934ac 100644 --- a/src/inferno/system/textareasystem.h +++ b/src/inferno/system/textareasystem.h @@ -33,7 +33,7 @@ public: void setScene(Scene* scene) { m_scene = scene; } private: - std::optional calculateCharacterQuad(unsigned char character, std::shared_ptr font, float& advance); + std::optional calculateCharacterQuad(unsigned char character, unsigned char previous, std::shared_ptr font, float& advanceX, float& advanceY); Scene* m_scene { nullptr }; };