Engine: Allow configuration of font size and line spacing
This commit is contained in:
@@ -42,9 +42,9 @@
|
|||||||
"name": "Text",
|
"name": "Text",
|
||||||
"content": "Hello World!",
|
"content": "Hello World!",
|
||||||
"font": "assets/fnt/open-sans",
|
"font": "assets/fnt/open-sans",
|
||||||
"font-size": 10,
|
"font-size": 24,
|
||||||
"width": 150,
|
"line-spacing": 1.0,
|
||||||
"lines": 3
|
"width": 150
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
if (json.exists("font-size") && json.at("font-size").type() == ruc::Json::Type::Number) {
|
||||||
json.at("font-size").getTo(value.fontSize);
|
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) {
|
if (json.exists("width") && json.at("width").type() == ruc::Json::Type::Number) {
|
||||||
json.at("width").getTo(value.width);
|
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
|
} // namespace Inferno
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ namespace Inferno {
|
|||||||
struct TextAreaComponent {
|
struct TextAreaComponent {
|
||||||
std::string content;
|
std::string content;
|
||||||
std::string font;
|
std::string font;
|
||||||
uint32_t fontSize { 0 };
|
unsigned char fontSize { 0 };
|
||||||
|
float lineSpacing { 1.0f };
|
||||||
uint32_t width { 0 };
|
uint32_t width { 0 };
|
||||||
uint32_t lines { 0 };
|
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
TextAreaComponent() {}
|
TextAreaComponent() {}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ void Font::parseFont(const std::string& font)
|
|||||||
// ---------------------------------
|
// ---------------------------------
|
||||||
|
|
||||||
if (action.compare("info") == 0) {
|
if (action.compare("info") == 0) {
|
||||||
m_size = convert<uint32_t>(findValue("size", columns));
|
m_size = convert<unsigned char>(findValue("size", columns));
|
||||||
auto paddings = findValue("padding", columns) | std::views::split(',');
|
auto paddings = findValue("padding", columns) | std::views::split(',');
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
for (const auto& padding : paddings) {
|
for (const auto& padding : paddings) {
|
||||||
@@ -81,6 +81,7 @@ void Font::parseFont(const std::string& font)
|
|||||||
uint32_t width = convert<uint32_t>(findValue("width", columns));
|
uint32_t width = convert<uint32_t>(findValue("width", columns));
|
||||||
uint32_t height = convert<uint32_t>(findValue("height", columns));
|
uint32_t height = convert<uint32_t>(findValue("height", columns));
|
||||||
Character character = {
|
Character character = {
|
||||||
|
.id = id,
|
||||||
.position = {
|
.position = {
|
||||||
convert<uint32_t>(findValue("x", columns)) + m_padding[Padding::Left],
|
convert<uint32_t>(findValue("x", columns)) + m_padding[Padding::Left],
|
||||||
convert<uint32_t>(findValue("y", columns)) + m_padding[Padding::Top],
|
convert<uint32_t>(findValue("y", columns)) + m_padding[Padding::Top],
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ namespace Inferno {
|
|||||||
class Texture;
|
class Texture;
|
||||||
|
|
||||||
struct Character {
|
struct Character {
|
||||||
|
char id; // 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
|
||||||
@@ -61,7 +62,7 @@ private:
|
|||||||
std::string findValue(const std::string& key, const std::vector<std::string>& columns) const;
|
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 = { 0 };
|
unsigned char m_size = { 0 };
|
||||||
uint32_t m_lineSpacing = { 0 };
|
uint32_t m_lineSpacing = { 0 };
|
||||||
std::array<uint32_t, 4> m_padding = { 0 };
|
std::array<uint32_t, 4> m_padding = { 0 };
|
||||||
std::shared_ptr<Texture> m_texture;
|
std::shared_ptr<Texture> m_texture;
|
||||||
|
|||||||
@@ -48,26 +48,87 @@ 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;
|
||||||
|
|
||||||
unsigned char previous = 0;
|
m_characters.clear();
|
||||||
float advanceX = 0.0f;
|
createLines(font, textarea);
|
||||||
float advanceY = 0.0f;
|
createQuads(font, textarea);
|
||||||
for (auto character : textarea.content) {
|
|
||||||
std::optional<CharacterQuad> quad = calculateCharacterQuad(character, previous, font, advanceX, advanceY);
|
|
||||||
previous = character;
|
|
||||||
|
|
||||||
if (quad) {
|
|
||||||
RendererCharacter::the().drawCharacter(quad.value(), font->texture());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<CharacterQuad> TextAreaSystem::calculateCharacterQuad(unsigned char character, unsigned char previous, std::shared_ptr<Font> font, float& advanceX, float& advanceY)
|
using Characters = std::vector<std::shared_ptr<Character>>;
|
||||||
|
|
||||||
|
void TextAreaSystem::createLines(std::shared_ptr<Font> font, const TextAreaComponent& textarea)
|
||||||
|
{
|
||||||
|
float fontScale = textarea.fontSize / (float)font->size();
|
||||||
|
|
||||||
|
// 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");
|
||||||
|
|
||||||
|
// -------------------------------------
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextAreaSystem::createQuads(std::shared_ptr<Font> font, const TextAreaComponent& textarea)
|
||||||
|
{
|
||||||
|
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<CharacterQuad> quad = calculateCharacterQuad(character, previous, font, fontScale, advanceX, advanceY);
|
||||||
|
if (quad) {
|
||||||
|
RendererCharacter::the().drawCharacter(quad.value(), font->texture());
|
||||||
|
}
|
||||||
|
|
||||||
|
previous = character->id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<CharacterQuad> TextAreaSystem::calculateCharacterQuad(std::shared_ptr<Character> c, char previous, std::shared_ptr<Font> font, float fontScale, float& advanceX, float& advanceY)
|
||||||
{
|
{
|
||||||
CharacterQuad characterQuad;
|
CharacterQuad characterQuad;
|
||||||
|
|
||||||
auto c = font->get(character);
|
|
||||||
|
|
||||||
// Texture
|
// Texture
|
||||||
// -------------------------------------
|
// -------------------------------------
|
||||||
|
|
||||||
@@ -78,7 +139,7 @@ std::optional<CharacterQuad> TextAreaSystem::calculateCharacterQuad(unsigned cha
|
|||||||
// Skip empty characters (like space)
|
// 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
|
||||||
advanceX += c->advance;
|
advanceX += c->advance * fontScale;
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,19 +148,13 @@ std::optional<CharacterQuad> TextAreaSystem::calculateCharacterQuad(unsigned cha
|
|||||||
|
|
||||||
// Kerning
|
// Kerning
|
||||||
if (c->kernings.find(previous) != c->kernings.end()) {
|
if (c->kernings.find(previous) != c->kernings.end()) {
|
||||||
advanceX += c->kernings.at(previous);
|
advanceX += c->kernings.at(previous) * fontScale;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Line wrapping
|
glm::vec2 cursor = { advanceX + (c->offset.x * fontScale),
|
||||||
if (advanceX + c->offset.x + c->size.x > textureWidth) {
|
advanceY - (c->offset.y * fontScale) };
|
||||||
advanceX = 0;
|
glm::vec2 cursorMax = { cursor.x + (c->size.x * fontScale),
|
||||||
advanceY -= font->lineSpacing();
|
cursor.y - (c->size.y * 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 };
|
|
||||||
|
|
||||||
// Scale the values from 0:512 (texture size) to -1:1 (screen space)
|
// Scale the values from 0:512 (texture size) to -1:1 (screen space)
|
||||||
glm::vec2 cursorScreen = {
|
glm::vec2 cursorScreen = {
|
||||||
@@ -117,7 +172,7 @@ std::optional<CharacterQuad> TextAreaSystem::calculateCharacterQuad(unsigned cha
|
|||||||
characterQuad.at(3).quad.position = { cursorScreen.x, cursorScreen.y, 0.0f }; // top left
|
characterQuad.at(3).quad.position = { cursorScreen.x, cursorScreen.y, 0.0f }; // top left
|
||||||
|
|
||||||
// Jump to the next glyph
|
// Jump to the next glyph
|
||||||
advanceX += c->advance;
|
advanceX += c->advance * fontScale;
|
||||||
|
|
||||||
// Texture coordinates
|
// Texture coordinates
|
||||||
// -------------------------------------
|
// -------------------------------------
|
||||||
|
|||||||
@@ -14,10 +14,13 @@
|
|||||||
#include "glm/ext/vector_float3.hpp" // glm::vec3
|
#include "glm/ext/vector_float3.hpp" // glm::vec3
|
||||||
#include "ruc/singleton.h"
|
#include "ruc/singleton.h"
|
||||||
|
|
||||||
|
#include "inferno/component/textareacomponent.h"
|
||||||
|
#include "inferno/render/font.h"
|
||||||
#include "inferno/render/renderer.h"
|
#include "inferno/render/renderer.h"
|
||||||
|
|
||||||
namespace Inferno {
|
namespace Inferno {
|
||||||
|
|
||||||
|
using Characters = std::vector<std::shared_ptr<Character>>;
|
||||||
using CharacterQuad = std::array<CharacterVertex, Renderer::vertexPerQuad>;
|
using CharacterQuad = std::array<CharacterVertex, Renderer::vertexPerQuad>;
|
||||||
|
|
||||||
class Font;
|
class Font;
|
||||||
@@ -33,8 +36,12 @@ public:
|
|||||||
void setScene(Scene* scene) { m_scene = scene; }
|
void setScene(Scene* scene) { m_scene = scene; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::optional<CharacterQuad> calculateCharacterQuad(unsigned char character, unsigned char previous, std::shared_ptr<Font> font, float& advanceX, float& advanceY);
|
void createLines(std::shared_ptr<Font> font, const TextAreaComponent& textarea);
|
||||||
|
void createQuads(std::shared_ptr<Font> font, const TextAreaComponent& textarea);
|
||||||
|
|
||||||
|
std::optional<CharacterQuad> calculateCharacterQuad(std::shared_ptr<Character> c, char previous, std::shared_ptr<Font> font, float fontSize, float& advanceX, float& advanceY);
|
||||||
|
|
||||||
|
Characters m_characters;
|
||||||
Scene* m_scene { nullptr };
|
Scene* m_scene { nullptr };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user