From 15b71f8387921e7f3db363a5aabab1f069cc8baf Mon Sep 17 00:00:00 2001 From: Riyyi Date: Fri, 29 Dec 2023 22:12:13 +0100 Subject: [PATCH] Engine: Allow configuration of font size and line spacing --- assets/scene/scene1.json | 6 +- src/inferno/component/textareacomponent.cpp | 6 +- src/inferno/component/textareacomponent.h | 4 +- src/inferno/render/font.cpp | 3 +- src/inferno/render/font.h | 3 +- src/inferno/system/textareasystem.cpp | 107 +++++++++++++++----- src/inferno/system/textareasystem.h | 9 +- 7 files changed, 101 insertions(+), 37 deletions(-) diff --git a/assets/scene/scene1.json b/assets/scene/scene1.json index 468e617..1f05ad9 100644 --- a/assets/scene/scene1.json +++ b/assets/scene/scene1.json @@ -42,9 +42,9 @@ "name": "Text", "content": "Hello World!", "font": "assets/fnt/open-sans", - "font-size": 10, - "width": 150, - "lines": 3 + "font-size": 24, + "line-spacing": 1.0, + "width": 150 } ] } diff --git a/src/inferno/component/textareacomponent.cpp b/src/inferno/component/textareacomponent.cpp index 1516ae6..75048df 100644 --- a/src/inferno/component/textareacomponent.cpp +++ b/src/inferno/component/textareacomponent.cpp @@ -23,12 +23,12 @@ void fromJson(const ruc::Json& json, TextAreaComponent& value) if (json.exists("font-size") && json.at("font-size").type() == ruc::Json::Type::Number) { json.at("font-size").getTo(value.fontSize); } + if (json.exists("line-spacing") && json.at("line-spacing").type() == ruc::Json::Type::Number) { + json.at("line-spacing").getTo(value.lineSpacing); + } if (json.exists("width") && json.at("width").type() == ruc::Json::Type::Number) { json.at("width").getTo(value.width); } - if (json.exists("lines") && json.at("lines").type() == ruc::Json::Type::Number) { - json.at("lines").getTo(value.lines); - } } } // namespace Inferno diff --git a/src/inferno/component/textareacomponent.h b/src/inferno/component/textareacomponent.h index 9759e57..b80d326 100644 --- a/src/inferno/component/textareacomponent.h +++ b/src/inferno/component/textareacomponent.h @@ -18,9 +18,9 @@ namespace Inferno { struct TextAreaComponent { std::string content; std::string font; - uint32_t fontSize { 0 }; + unsigned char fontSize { 0 }; + float lineSpacing { 1.0f }; uint32_t width { 0 }; - uint32_t lines { 0 }; #if 0 TextAreaComponent() {} diff --git a/src/inferno/render/font.cpp b/src/inferno/render/font.cpp index 264800e..921c00d 100644 --- a/src/inferno/render/font.cpp +++ b/src/inferno/render/font.cpp @@ -55,7 +55,7 @@ void Font::parseFont(const std::string& font) // --------------------------------- if (action.compare("info") == 0) { - m_size = convert(findValue("size", columns)); + m_size = convert(findValue("size", columns)); auto paddings = findValue("padding", columns) | std::views::split(','); size_t i = 0; for (const auto& padding : paddings) { @@ -81,6 +81,7 @@ void Font::parseFont(const std::string& font) uint32_t width = convert(findValue("width", columns)); uint32_t height = convert(findValue("height", columns)); Character character = { + .id = id, .position = { convert(findValue("x", columns)) + m_padding[Padding::Left], convert(findValue("y", columns)) + m_padding[Padding::Top], diff --git a/src/inferno/render/font.h b/src/inferno/render/font.h index 41e0cf6..b877b5d 100644 --- a/src/inferno/render/font.h +++ b/src/inferno/render/font.h @@ -25,6 +25,7 @@ namespace Inferno { class Texture; struct Character { + char id; // Character glm::uvec2 position; // Position glm::uvec2 size; // Width/height glm::ivec2 offset; // Offset from baseline to left / top of glyph @@ -61,7 +62,7 @@ private: std::string findValue(const std::string& key, const std::vector& columns) const; std::string m_name; - uint32_t m_size = { 0 }; + unsigned char m_size = { 0 }; uint32_t m_lineSpacing = { 0 }; std::array m_padding = { 0 }; std::shared_ptr m_texture; diff --git a/src/inferno/system/textareasystem.cpp b/src/inferno/system/textareasystem.cpp index 4d3e71f..b8b9c4d 100644 --- a/src/inferno/system/textareasystem.cpp +++ b/src/inferno/system/textareasystem.cpp @@ -48,25 +48,86 @@ void TextAreaSystem::render() std::shared_ptr font = FontManager::the().load(textarea.font); // glm::mat4 translate = transform.translate; - unsigned char previous = 0; - float advanceX = 0.0f; - float advanceY = 0.0f; - for (auto character : textarea.content) { - std::optional quad = calculateCharacterQuad(character, previous, font, advanceX, advanceY); - previous = character; - - if (quad) { - RendererCharacter::the().drawCharacter(quad.value(), font->texture()); - } + m_characters.clear(); + createLines(font, textarea); + createQuads(font, textarea); + } +} + +using Characters = std::vector>; + +void TextAreaSystem::createLines(std::shared_ptr font, const TextAreaComponent& textarea) +{ + float fontScale = textarea.fontSize / (float)font->size(); + + // Texture + // ------------------------------------- + + float textureWidth = static_cast(font->texture()->width()); + float textureHeight = static_cast(font->texture()->height()); + VERIFY(textureWidth == textureHeight, "TextAreaSystem read invalid font texture"); + + // ------------------------------------- + + char previous = 0; + size_t spaceIndex = 0; + float lineWidth = 0.0f; + float lineWidthSinceLastSpace = 0.0f; + for (char character : textarea.content) { + auto c = font->get(character); + m_characters.push_back(c); + + // Kerning + char kerning = 0; + if (c->kernings.find(previous) != c->kernings.end()) { + kerning = c->kernings.at(previous); + } + + lineWidth += (c->advance + c->offset.x + kerning) * fontScale; + lineWidthSinceLastSpace += (c->advance + c->offset.x + kerning) * fontScale; + + if (character == ' ') { + spaceIndex = m_characters.size() - 1; + lineWidthSinceLastSpace = 0; + } + + if (lineWidth > textureWidth) { + m_characters[spaceIndex] = nullptr; + lineWidth = 0; + lineWidth = lineWidthSinceLastSpace; } + + previous = character; } } -std::optional TextAreaSystem::calculateCharacterQuad(unsigned char character, unsigned char previous, std::shared_ptr font, float& advanceX, float& advanceY) +void TextAreaSystem::createQuads(std::shared_ptr font, const TextAreaComponent& textarea) { - CharacterQuad characterQuad; + float fontScale = textarea.fontSize / (float)font->size(); + + char previous = 0; + float advanceX = 0.0f; + float advanceY = 0.0f; + for (const auto& character : m_characters) { + // Go to the next line on "\n" + if (character == nullptr) { + advanceX = 0; + advanceY -= (font->lineSpacing() * textarea.lineSpacing) * fontScale; + continue; + } + + std::optional quad = calculateCharacterQuad(character, previous, font, fontScale, advanceX, advanceY); + if (quad) { + RendererCharacter::the().drawCharacter(quad.value(), font->texture()); + } + + previous = character->id; + } +} - auto c = font->get(character); +std::optional TextAreaSystem::calculateCharacterQuad(std::shared_ptr c, char previous, std::shared_ptr font, float fontScale, float& advanceX, float& advanceY) +{ + CharacterQuad characterQuad; // Texture // ------------------------------------- @@ -78,7 +139,7 @@ std::optional TextAreaSystem::calculateCharacterQuad(unsigned cha // Skip empty characters (like space) if (c->size.x == 0 || c->size.y == 0) { // Jump to the next glyph - advanceX += c->advance; + advanceX += c->advance * fontScale; return {}; } @@ -87,19 +148,13 @@ std::optional TextAreaSystem::calculateCharacterQuad(unsigned cha // Kerning if (c->kernings.find(previous) != c->kernings.end()) { - advanceX += c->kernings.at(previous); - } - - // Line wrapping - if (advanceX + c->offset.x + c->size.x > textureWidth) { - advanceX = 0; - advanceY -= font->lineSpacing(); + advanceX += c->kernings.at(previous) * fontScale; } - 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 }; + glm::vec2 cursor = { advanceX + (c->offset.x * fontScale), + advanceY - (c->offset.y * fontScale) }; + glm::vec2 cursorMax = { cursor.x + (c->size.x * fontScale), + cursor.y - (c->size.y * fontScale) }; // Scale the values from 0:512 (texture size) to -1:1 (screen space) glm::vec2 cursorScreen = { @@ -117,7 +172,7 @@ std::optional TextAreaSystem::calculateCharacterQuad(unsigned cha characterQuad.at(3).quad.position = { cursorScreen.x, cursorScreen.y, 0.0f }; // top left // Jump to the next glyph - advanceX += c->advance; + advanceX += c->advance * fontScale; // Texture coordinates // ------------------------------------- diff --git a/src/inferno/system/textareasystem.h b/src/inferno/system/textareasystem.h index c1934ac..e54c7bb 100644 --- a/src/inferno/system/textareasystem.h +++ b/src/inferno/system/textareasystem.h @@ -14,10 +14,13 @@ #include "glm/ext/vector_float3.hpp" // glm::vec3 #include "ruc/singleton.h" +#include "inferno/component/textareacomponent.h" +#include "inferno/render/font.h" #include "inferno/render/renderer.h" namespace Inferno { +using Characters = std::vector>; using CharacterQuad = std::array; class Font; @@ -33,8 +36,12 @@ public: void setScene(Scene* scene) { m_scene = scene; } private: - std::optional calculateCharacterQuad(unsigned char character, unsigned char previous, std::shared_ptr font, float& advanceX, float& advanceY); + void createLines(std::shared_ptr font, const TextAreaComponent& textarea); + void createQuads(std::shared_ptr font, const TextAreaComponent& textarea); + std::optional calculateCharacterQuad(std::shared_ptr c, char previous, std::shared_ptr font, float fontSize, float& advanceX, float& advanceY); + + Characters m_characters; Scene* m_scene { nullptr }; };