From d0ed49d192b16f1ffc89cb59f978ec3ee529af6f Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Sun, 22 Mar 2026 12:47:32 +0100 Subject: [PATCH] revisada i actualitzada la classe Text per a donar suport a utf-8 --- config/assets.yaml | 10 +- data/font/8bithud.fnt | 135 ++++++++++++++ data/font/aseprite.fnt | 135 ++++++++++++++ data/font/gauntlet.fnt | 135 ++++++++++++++ data/font/smb2.fnt | 144 +++++++++++++++ data/font/subatomic.fnt | 135 ++++++++++++++ source/core/rendering/screen.cpp | 2 +- source/core/rendering/text.cpp | 217 ++++++++++++++--------- source/core/rendering/text.hpp | 36 ++-- source/core/resources/resource_cache.cpp | 10 +- source/game/scenes/title.cpp | 22 ++- source/game/scenes/title.hpp | 8 +- 12 files changed, 861 insertions(+), 128 deletions(-) create mode 100644 data/font/8bithud.fnt create mode 100644 data/font/aseprite.fnt create mode 100644 data/font/gauntlet.fnt create mode 100644 data/font/smb2.fnt create mode 100644 data/font/subatomic.fnt diff --git a/config/assets.yaml b/config/assets.yaml index b48470a..2c0c87c 100644 --- a/config/assets.yaml +++ b/config/assets.yaml @@ -7,23 +7,23 @@ assets: - type: BITMAP path: ${PREFIX}/data/font/smb2.gif - type: FONT - path: ${PREFIX}/data/font/smb2.txt + path: ${PREFIX}/data/font/smb2.fnt - type: BITMAP path: ${PREFIX}/data/font/aseprite.gif - type: FONT - path: ${PREFIX}/data/font/aseprite.txt + path: ${PREFIX}/data/font/aseprite.fnt - type: BITMAP path: ${PREFIX}/data/font/gauntlet.gif - type: FONT - path: ${PREFIX}/data/font/gauntlet.txt + path: ${PREFIX}/data/font/gauntlet.fnt - type: BITMAP path: ${PREFIX}/data/font/subatomic.gif - type: FONT - path: ${PREFIX}/data/font/subatomic.txt + path: ${PREFIX}/data/font/subatomic.fnt - type: BITMAP path: ${PREFIX}/data/font/8bithud.gif - type: FONT - path: ${PREFIX}/data/font/8bithud.txt + path: ${PREFIX}/data/font/8bithud.fnt # PALETTES palettes: diff --git a/data/font/8bithud.fnt b/data/font/8bithud.fnt new file mode 100644 index 0000000..66959c6 --- /dev/null +++ b/data/font/8bithud.fnt @@ -0,0 +1,135 @@ +# Font: 8bithud +# Formato: codepoint_decimal ancho_visual +# Los gliphos se listan en orden de aparición en el bitmap (izquierda→derecha, arriba→abajo) + +box_width 8 +box_height 8 +columns 15 + +# ASCII 32-126 +32 2 +33 2 +34 5 +35 6 +36 6 +37 6 +38 6 +39 2 +40 3 +41 3 +42 4 +43 3 +44 2 +45 3 +46 2 +47 4 +48 6 +49 6 +50 6 +51 6 +52 6 +53 6 +54 6 +55 6 +56 6 +57 6 +58 2 +59 2 +60 4 +61 3 +62 4 +63 6 +64 8 +65 6 +66 6 +67 6 +68 6 +69 6 +70 6 +71 6 +72 6 +73 6 +74 6 +75 6 +76 6 +77 6 +78 6 +79 6 +80 6 +81 6 +82 6 +83 6 +84 6 +85 6 +86 5 +87 6 +88 6 +89 6 +90 6 +91 3 +92 5 +93 3 +94 4 +95 6 +96 2 +97 5 +98 5 +99 5 +100 5 +101 5 +102 5 +103 5 +104 5 +105 4 +106 5 +107 5 +108 5 +109 6 +110 5 +111 5 +112 5 +113 5 +114 5 +115 5 +116 4 +117 5 +118 5 +119 6 +120 4 +121 4 +122 5 +123 3 +124 2 +125 3 +126 3 + +# Extensiones para ES/CA/VA (descomentar tras añadirlos al bitmap) +# 192 6 # À +# 193 6 # Á +# 200 6 # È +# 201 6 # É +# 205 6 # Í +# 207 6 # Ï +# 210 6 # Ò +# 211 6 # Ó +# 218 6 # Ú +# 220 6 # Ü +# 209 6 # Ñ +# 199 6 # Ç +# 224 5 # à +# 225 5 # á +# 232 5 # è +# 233 5 # é +# 237 4 # í +# 239 5 # ï +# 242 5 # ò +# 243 5 # ó +# 250 5 # ú +# 252 5 # ü +# 241 5 # ñ +# 231 5 # ç +# 161 2 # ¡ +# 191 6 # ¿ +# 171 6 # « +# 187 6 # » +# 183 2 # · (punt volat) diff --git a/data/font/aseprite.fnt b/data/font/aseprite.fnt new file mode 100644 index 0000000..a29a6f1 --- /dev/null +++ b/data/font/aseprite.fnt @@ -0,0 +1,135 @@ +# Font: aseprite +# Formato: codepoint_decimal ancho_visual +# Los gliphos se listan en orden de aparición en el bitmap (izquierda→derecha, arriba→abajo) + +box_width 8 +box_height 8 +columns 15 + +# ASCII 32-126 +32 3 +33 1 +34 3 +35 3 +36 4 +37 5 +38 5 +39 2 +40 2 +41 2 +42 5 +43 5 +44 3 +45 3 +46 1 +47 4 +48 4 +49 2 +50 4 +51 4 +52 4 +53 4 +54 4 +55 4 +56 4 +57 4 +58 1 +59 1 +60 3 +61 4 +62 4 +63 4 +64 7 +65 4 +66 4 +67 4 +68 4 +69 4 +70 4 +71 4 +72 4 +73 2 +74 2 +75 4 +76 4 +77 5 +78 4 +79 5 +80 4 +81 5 +82 4 +83 4 +84 5 +85 4 +86 5 +87 7 +88 5 +89 5 +90 4 +91 2 +92 3 +93 2 +94 5 +95 5 +96 3 +97 4 +98 4 +99 4 +100 4 +101 4 +102 2 +103 4 +104 4 +105 1 +106 2 +107 4 +108 1 +109 7 +110 4 +111 4 +112 4 +113 4 +114 3 +115 3 +116 2 +117 4 +118 4 +119 5 +120 5 +121 4 +122 4 +123 3 +124 3 +125 3 +126 5 + +# Extensiones para ES/CA/VA (descomentar tras añadirlos al bitmap) +# 192 4 # À +# 193 4 # Á +# 200 4 # È +# 201 4 # É +# 205 2 # Í +# 207 2 # Ï +# 210 5 # Ò +# 211 5 # Ó +# 218 4 # Ú +# 220 4 # Ü +# 209 4 # Ñ +# 199 4 # Ç +# 224 4 # à +# 225 4 # á +# 232 4 # è +# 233 4 # é +# 237 1 # í +# 239 2 # ï +# 242 4 # ò +# 243 4 # ó +# 250 4 # ú +# 252 4 # ü +# 241 4 # ñ +# 231 4 # ç +# 161 1 # ¡ +# 191 4 # ¿ +# 171 5 # « +# 187 5 # » +# 183 1 # · (punt volat) diff --git a/data/font/gauntlet.fnt b/data/font/gauntlet.fnt new file mode 100644 index 0000000..f942580 --- /dev/null +++ b/data/font/gauntlet.fnt @@ -0,0 +1,135 @@ +# Font: gauntlet +# Formato: codepoint_decimal ancho_visual +# Los gliphos se listan en orden de aparición en el bitmap (izquierda→derecha, arriba→abajo) + +box_width 8 +box_height 8 +columns 15 + +# ASCII 32-126 +32 6 +33 2 +34 5 +35 6 +36 6 +37 7 +38 7 +39 2 +40 4 +41 4 +42 6 +43 8 +44 2 +45 7 +46 2 +47 7 +48 7 +49 6 +50 6 +51 6 +52 7 +53 6 +54 6 +55 6 +56 6 +57 6 +58 2 +59 2 +60 5 +61 6 +62 5 +63 6 +64 6 +65 6 +66 7 +67 7 +68 7 +69 7 +70 7 +71 7 +72 6 +73 6 +74 7 +75 7 +76 7 +77 7 +78 7 +79 7 +80 7 +81 7 +82 7 +83 6 +84 6 +85 6 +86 6 +87 7 +88 7 +89 6 +90 7 +91 8 +92 3 +93 7 +94 7 +95 8 +96 0 +97 6 +98 7 +99 7 +100 7 +101 7 +102 7 +103 7 +104 6 +105 6 +106 7 +107 7 +108 7 +109 7 +110 7 +111 7 +112 7 +113 7 +114 7 +115 6 +116 6 +117 6 +118 6 +119 7 +120 7 +121 6 +122 7 +123 0 +124 0 +125 0 +126 0 + +# Extensiones para ES/CA/VA (descomentar tras añadirlos al bitmap) +# 192 6 # À +# 193 6 # Á +# 200 7 # È +# 201 7 # É +# 205 6 # Í +# 207 6 # Ï +# 210 7 # Ò +# 211 7 # Ó +# 218 6 # Ú +# 220 6 # Ü +# 209 7 # Ñ +# 199 7 # Ç +# 224 6 # à +# 225 6 # á +# 232 7 # è +# 233 7 # é +# 237 6 # í +# 239 6 # ï +# 242 7 # ò +# 243 7 # ó +# 250 6 # ú +# 252 6 # ü +# 241 7 # ñ +# 231 7 # ç +# 161 2 # ¡ +# 191 6 # ¿ +# 171 7 # « +# 187 7 # » +# 183 2 # · (punt volat) diff --git a/data/font/smb2.fnt b/data/font/smb2.fnt new file mode 100644 index 0000000..31deeb0 --- /dev/null +++ b/data/font/smb2.fnt @@ -0,0 +1,144 @@ +# Font: smb2 - Super Mario Bros 2 +# Formato: codepoint_decimal ancho_visual +# Los gliphos se listan en orden de aparición en el bitmap (izquierda→derecha, arriba→abajo) +# Para añadir nuevos caracteres: añadir entradas al final Y dibujarlos en el bitmap GIF + +box_width 8 +box_height 8 +columns 15 + +# ASCII 32-126 (misma disposición que el bitmap original) +32 7 +33 7 +34 7 +35 7 +36 7 +37 7 +38 7 +39 7 +40 7 +41 7 +42 7 +43 7 +44 7 +45 7 +46 7 +47 7 +48 7 +49 7 +50 7 +51 7 +52 7 +53 7 +54 7 +55 7 +56 7 +57 7 +58 7 +59 7 +60 7 +61 7 +62 7 +63 7 +64 7 +65 7 +66 7 +67 7 +68 7 +69 7 +70 7 +71 7 +72 7 +73 7 +74 7 +75 7 +76 7 +77 7 +78 7 +79 7 +80 7 +81 7 +82 7 +83 7 +84 7 +85 7 +86 7 +87 7 +88 7 +89 7 +90 7 +91 7 +92 7 +93 7 +94 7 +95 7 +96 7 +97 7 +98 7 +99 7 +100 7 +101 7 +102 7 +103 7 +104 7 +105 7 +106 7 +107 7 +108 7 +109 7 +110 7 +111 7 +112 7 +113 7 +114 7 +115 7 +116 7 +117 7 +118 7 +119 7 +120 7 +121 7 +122 7 +123 7 +124 7 +125 7 +126 7 + +# Extensiones para ES/CA/VA +# Para activarlos: dibujar los gliphos en el bitmap GIF a continuación del último ASCII +# y descomentar las entradas correspondientes: +# +# Mayúsculas acentuadas +# 192 7 # À +# 193 7 # Á +# 200 7 # È +# 201 7 # É +# 205 7 # Í +# 207 7 # Ï +# 210 7 # Ò +# 211 7 # Ó +# 218 7 # Ú +# 220 7 # Ü +# 209 7 # Ñ +# 199 7 # Ç +# +# Minúsculas acentuadas +# 224 7 # à +# 225 7 # á +# 232 7 # è +# 233 7 # é +# 237 7 # í +# 239 7 # ï +# 242 7 # ò +# 243 7 # ó +# 250 7 # ú +# 252 7 # ü +# 241 7 # ñ +# 231 7 # ç +# +# Símbolos adicionales +# 161 7 # ¡ +# 191 7 # ¿ +# 171 7 # « +# 187 7 # » +# 183 7 # · (punt volat) diff --git a/data/font/subatomic.fnt b/data/font/subatomic.fnt new file mode 100644 index 0000000..e3602f7 --- /dev/null +++ b/data/font/subatomic.fnt @@ -0,0 +1,135 @@ +# Font: subatomic +# Formato: codepoint_decimal ancho_visual +# Los gliphos se listan en orden de aparición en el bitmap (izquierda→derecha, arriba→abajo) + +box_width 7 +box_height 7 +columns 15 + +# ASCII 32-126 +32 4 +33 1 +34 3 +35 5 +36 5 +37 5 +38 6 +39 1 +40 2 +41 2 +42 5 +43 5 +44 1 +45 5 +46 1 +47 5 +48 5 +49 2 +50 5 +51 5 +52 5 +53 5 +54 5 +55 5 +56 5 +57 5 +58 1 +59 2 +60 3 +61 5 +62 3 +63 4 +64 5 +65 5 +66 5 +67 5 +68 5 +69 4 +70 5 +71 5 +72 5 +73 1 +74 5 +75 5 +76 2 +77 5 +78 5 +79 5 +80 5 +81 5 +82 5 +83 5 +84 5 +85 5 +86 5 +87 5 +88 5 +89 5 +90 5 +91 2 +92 5 +93 2 +94 3 +95 5 +96 2 +97 4 +98 4 +99 3 +100 4 +101 4 +102 3 +103 4 +104 4 +105 1 +106 2 +107 3 +108 1 +109 5 +110 4 +111 4 +112 4 +113 4 +114 3 +115 4 +116 2 +117 4 +118 4 +119 5 +120 3 +121 4 +122 4 +123 0 +124 0 +125 0 +126 0 + +# Extensiones para ES/CA/VA (descomentar tras añadirlos al bitmap) +# 192 5 # À +# 193 5 # Á +# 200 4 # È +# 201 4 # É +# 205 1 # Í +# 207 2 # Ï +# 210 5 # Ò +# 211 5 # Ó +# 218 5 # Ú +# 220 5 # Ü +# 209 5 # Ñ +# 199 5 # Ç +# 224 4 # à +# 225 4 # á +# 232 4 # è +# 233 4 # é +# 237 1 # í +# 239 2 # ï +# 242 4 # ò +# 243 4 # ó +# 250 4 # ú +# 252 4 # ü +# 241 4 # ñ +# 231 3 # ç +# 161 1 # ¡ +# 191 4 # ¿ +# 171 5 # « +# 187 5 # » +# 183 1 # · (punt volat) diff --git a/source/core/rendering/screen.cpp b/source/core/rendering/screen.cpp index 6439625..c7462c3 100644 --- a/source/core/rendering/screen.cpp +++ b/source/core/rendering/screen.cpp @@ -602,5 +602,5 @@ void Screen::createText() { auto surface = std::make_shared(Resource::List::get()->get("aseprite.gif")); // Crea el objeto de texto (el constructor de Text carga el archivo text_file internamente) - text_ = std::make_shared(surface, Resource::List::get()->get("aseprite.txt")); + text_ = std::make_shared(surface, Resource::List::get()->get("aseprite.fnt")); } \ No newline at end of file diff --git a/source/core/rendering/text.cpp b/source/core/rendering/text.cpp index e1bd377..0e13ef0 100644 --- a/source/core/rendering/text.cpp +++ b/source/core/rendering/text.cpp @@ -3,7 +3,6 @@ #include #include // Para size_t -#include // Para basic_ifstream, basic_istream, basic_ostream #include // Para cerr #include // Para istringstream #include // Para runtime_error @@ -14,93 +13,114 @@ #include "core/resources/resource_helper.hpp" // Para ResourceHelper #include "utils/utils.hpp" // Para getFileName, stringToColor, printWithDots -// Llena una estructuta TextFile desde un fichero +// Extrae el siguiente codepoint UTF-8 de la cadena, avanzando 'pos' al byte siguiente +auto Text::nextCodepoint(const std::string& s, size_t& pos) -> uint32_t { + auto c = static_cast(s[pos]); + uint32_t cp = 0; + size_t extra = 0; + + if (c < 0x80) { cp = c; extra = 0; } + else if (c < 0xC0) { pos++; return 0xFFFD; } // byte de continuación suelto + else if (c < 0xE0) { cp = c & 0x1F; extra = 1; } + else if (c < 0xF0) { cp = c & 0x0F; extra = 2; } + else if (c < 0xF8) { cp = c & 0x07; extra = 3; } + else { pos++; return 0xFFFD; } + + pos++; + for (size_t i = 0; i < extra && pos < s.size(); ++i, ++pos) { + auto cb = static_cast(s[pos]); + if ((cb & 0xC0) != 0x80) { return 0xFFFD; } + cp = (cp << 6) | (cb & 0x3F); + } + return cp; +} + +// Convierte un codepoint Unicode a una cadena UTF-8 +auto Text::codepointToUtf8(uint32_t cp) -> std::string { + std::string result; + if (cp < 0x80) { + result += static_cast(cp); + } else if (cp < 0x800) { + result += static_cast(0xC0 | (cp >> 6)); + result += static_cast(0x80 | (cp & 0x3F)); + } else if (cp < 0x10000) { + result += static_cast(0xE0 | (cp >> 12)); + result += static_cast(0x80 | ((cp >> 6) & 0x3F)); + result += static_cast(0x80 | (cp & 0x3F)); + } else { + result += static_cast(0xF0 | (cp >> 18)); + result += static_cast(0x80 | ((cp >> 12) & 0x3F)); + result += static_cast(0x80 | ((cp >> 6) & 0x3F)); + result += static_cast(0x80 | (cp & 0x3F)); + } + return result; +} + +// Carga un fichero de definición de fuente .fnt +// Formato: líneas "clave valor", comentarios con #, gliphos como "codepoint ancho" auto Text::loadTextFile(const std::string& file_path) -> std::shared_ptr { auto tf = std::make_shared(); - // No es necesario inicializar - los miembros tienen valores por defecto - - // Load file using ResourceHelper (supports both filesystem and pack) auto file_data = Resource::Helper::loadFile(file_path); if (file_data.empty()) { std::cerr << "Error: Fichero no encontrado " << getFileName(file_path) << '\n'; throw std::runtime_error("Fichero no encontrado: " + getFileName(file_path)); } - // Convert bytes to string and parse std::string content(file_data.begin(), file_data.end()); std::istringstream stream(content); - std::string buffer; + std::string line; + int glyph_index = 0; - // Lee los dos primeros valores del fichero - std::getline(stream, buffer); - // Remove Windows line ending if present - if (!buffer.empty() && buffer.back() == '\r') { - buffer.pop_back(); - } - std::getline(stream, buffer); - // Remove Windows line ending if present - if (!buffer.empty() && buffer.back() == '\r') { - buffer.pop_back(); - } - tf->box_width = std::stoi(buffer); + while (std::getline(stream, line)) { + if (!line.empty() && line.back() == '\r') { line.pop_back(); } + if (line.empty() || line[0] == '#') { continue; } - std::getline(stream, buffer); - // Remove Windows line ending if present - if (!buffer.empty() && buffer.back() == '\r') { - buffer.pop_back(); - } - std::getline(stream, buffer); - // Remove Windows line ending if present - if (!buffer.empty() && buffer.back() == '\r') { - buffer.pop_back(); - } - tf->box_height = std::stoi(buffer); + std::istringstream ls(line); + std::string key; + ls >> key; - // lee el resto de datos del fichero - auto index = 32; - auto line_read = 0; - while (std::getline(stream, buffer)) { - // Remove Windows line ending if present - if (!buffer.empty() && buffer.back() == '\r') { - buffer.pop_back(); + if (key == "box_width") { + ls >> tf->box_width; + } else if (key == "box_height") { + ls >> tf->box_height; + } else if (key == "columns") { + ls >> tf->columns; + } else { + // Línea de glifo: codepoint_decimal ancho_visual + uint32_t codepoint = 0; + int width = 0; + try { + codepoint = static_cast(std::stoul(key)); + ls >> width; + } catch (...) { + continue; // línea mal formateada, ignorar + } + Offset off{}; + off.x = (glyph_index % tf->columns) * tf->box_width; + off.y = (glyph_index / tf->columns) * tf->box_height; + off.w = width; + tf->offset[codepoint] = off; + ++glyph_index; } - // Almacena solo las lineas impares - if (line_read % 2 == 1) { - tf->offset[index++].w = std::stoi(buffer); - } - - // Limpia el buffer - buffer.clear(); - line_read++; - }; + } printWithDots("Text File : ", getFileName(file_path), "[ LOADED ]"); - - // Establece las coordenadas para cada caracter ascii de la cadena y su ancho - for (int i = 32; i < 128; ++i) { - tf->offset[i].x = ((i - 32) % 15) * tf->box_width; - tf->offset[i].y = ((i - 32) / 15) * tf->box_height; - } - return tf; } -// Constructor +// Constructor desde fichero Text::Text(const std::shared_ptr& surface, const std::string& text_file) { - // Carga los offsets desde el fichero auto tf = loadTextFile(text_file); - // Inicializa variables desde la estructura box_height_ = tf->box_height; box_width_ = tf->box_width; offset_ = tf->offset; - // Crea los objetos sprite_ = std::make_unique(surface, (SDL_FRect){0.0F, 0.0F, static_cast(box_width_), static_cast(box_height_)}); } -// Constructor +// Constructor desde estructura precargada Text::Text(const std::shared_ptr& surface, const std::shared_ptr& text_file) : sprite_(std::make_unique(surface, (SDL_FRect){0.0F, 0.0F, static_cast(text_file->box_width), static_cast(text_file->box_height)})), box_width_(text_file->box_width), @@ -111,18 +131,22 @@ Text::Text(const std::shared_ptr& surface, const std::shared_ptr& // Escribe texto en pantalla void Text::write(int x, int y, const std::string& text, int kerning, int lenght) { int shift = 0; - - if (lenght == -1) { - lenght = text.length(); - } + int glyphs_done = 0; + size_t pos = 0; sprite_->setY(y); - for (int i = 0; i < lenght; ++i) { - auto index = static_cast(text[i]); - sprite_->setClip(offset_[index].x, offset_[index].y, box_width_, box_height_); - sprite_->setX(x + shift); - sprite_->render(1, 15); - shift += offset_[static_cast(text[i])].w + kerning; + while (pos < text.size()) { + if (lenght != -1 && glyphs_done >= lenght) { break; } + uint32_t cp = nextCodepoint(text, pos); + auto it = offset_.find(cp); + if (it == offset_.end()) { it = offset_.find('?'); } + if (it != offset_.end()) { + sprite_->setClip(it->second.x, it->second.y, box_width_, box_height_); + sprite_->setX(x + shift); + sprite_->render(1, 15); + shift += it->second.w + kerning; + } + ++glyphs_done; } } @@ -157,18 +181,22 @@ auto Text::writeDXToSurface(Uint8 flags, const std::string& text, int kerning, U // Escribe el texto con colores void Text::writeColored(int x, int y, const std::string& text, Uint8 color, int kerning, int lenght) { int shift = 0; - - if (lenght == -1) { - lenght = text.length(); - } + int glyphs_done = 0; + size_t pos = 0; sprite_->setY(y); - for (int i = 0; i < lenght; ++i) { - auto index = static_cast(text[i]); - sprite_->setClip(offset_[index].x, offset_[index].y, box_width_, box_height_); - sprite_->setX(x + shift); - sprite_->render(1, color); - shift += offset_[static_cast(text[i])].w + kerning; + while (pos < text.size()) { + if (lenght != -1 && glyphs_done >= lenght) { break; } + uint32_t cp = nextCodepoint(text, pos); + auto it = offset_.find(cp); + if (it == offset_.end()) { it = offset_.find('?'); } + if (it != offset_.end()) { + sprite_->setClip(it->second.x, it->second.y, box_width_, box_height_); + sprite_->setX(x + shift); + sprite_->render(1, color); + shift += it->second.w + kerning; + } + ++glyphs_done; } } @@ -188,8 +216,8 @@ void Text::writeCentered(int x, int y, const std::string& text, int kerning, int void Text::writeDX(Uint8 flags, int x, int y, const std::string& text, int kerning, Uint8 text_color, Uint8 shadow_distance, Uint8 shadow_color, int lenght) { const auto CENTERED = ((flags & CENTER_FLAG) == CENTER_FLAG); const auto SHADOWED = ((flags & SHADOW_FLAG) == SHADOW_FLAG); - const auto COLORED = ((flags & COLOR_FLAG) == COLOR_FLAG); - const auto STROKED = ((flags & STROKE_FLAG) == STROKE_FLAG); + const auto COLORED = ((flags & COLOR_FLAG) == COLOR_FLAG); + const auto STROKED = ((flags & STROKE_FLAG) == STROKE_FLAG); if (CENTERED) { x -= (Text::length(text, kerning) / 2); @@ -213,22 +241,35 @@ void Text::writeDX(Uint8 flags, int x, int y, const std::string& text, int kerni writeColored(x, y, text, text_color, kerning, lenght); } else { writeColored(x, y, text, text_color, kerning, lenght); - // write(x, y, text, kerning, lenght); } } -// Obtiene la longitud en pixels de una cadena +// Obtiene la longitud en pixels de una cadena UTF-8 auto Text::length(const std::string& text, int kerning) const -> int { int shift = 0; - for (size_t i = 0; i < text.length(); ++i) { - shift += (offset_[static_cast(text[i])].w + kerning); + size_t pos = 0; + + while (pos < text.size()) { + uint32_t cp = nextCodepoint(text, pos); + auto it = offset_.find(cp); + if (it == offset_.end()) { it = offset_.find('?'); } + if (it != offset_.end()) { + shift += it->second.w + kerning; + } } - // Descuenta el kerning del último caracter - return shift - kerning; + return shift > 0 ? shift - kerning : 0; } -// Devuelve el valor de la variable +// Devuelve el ancho en pixels de un glifo dado su codepoint Unicode +auto Text::glyphWidth(uint32_t codepoint, int kerning) const -> int { + auto it = offset_.find(codepoint); + if (it == offset_.end()) { it = offset_.find('?'); } + if (it != offset_.end()) { return it->second.w + kerning; } + return 0; +} + +// Devuelve el tamaño de la caja de cada caracter auto Text::getCharacterSize() const -> int { return box_width_; } @@ -236,4 +277,4 @@ auto Text::getCharacterSize() const -> int { // Establece si se usa un tamaño fijo de letra void Text::setFixedWidth(bool value) { fixed_width_ = value; -} \ No newline at end of file +} diff --git a/source/core/rendering/text.hpp b/source/core/rendering/text.hpp index 198eff3..6b99497 100644 --- a/source/core/rendering/text.hpp +++ b/source/core/rendering/text.hpp @@ -2,14 +2,14 @@ #include -#include // Para std::array -#include // Para shared_ptr, unique_ptr -#include // Para string +#include // Para shared_ptr, unique_ptr +#include // Para string +#include // Para unordered_map #include "core/rendering/surface_sprite.hpp" // Para SSprite -class Surface; // lines 8-8 +class Surface; // Forward declaration -// Clase texto. Pinta texto en pantalla a partir de un bitmap +// Clase texto. Pinta texto en pantalla a partir de un bitmap con soporte UTF-8 class Text { public: // Tipos anidados públicos @@ -18,9 +18,10 @@ class Text { }; struct File { - int box_width{0}; // Anchura de la caja de cada caracter en el png - int box_height{0}; // Altura de la caja de cada caracter en el png - std::array offset{}; // Vector con las posiciones y ancho de cada letra + int box_width{0}; // Anchura de la caja de cada caracter en el png + int box_height{0}; // Altura de la caja de cada caracter en el png + int columns{16}; // Número de columnas en el bitmap + std::unordered_map offset; // Posición y ancho de cada glifo (clave: codepoint Unicode) }; // Constructor @@ -45,20 +46,23 @@ class Text { auto writeToSurface(const std::string& text, int zoom = 1, int kerning = 1) -> std::shared_ptr; // Escribe el texto en una textura auto writeDXToSurface(Uint8 flags, const std::string& text, int kerning = 1, Uint8 text_color = Uint8(), Uint8 shadow_distance = 1, Uint8 shadow_color = Uint8(), int lenght = -1) -> std::shared_ptr; // Escribe el texto con extras en una textura - [[nodiscard]] auto length(const std::string& text, int kerning = 1) const -> int; // Obtiene la longitud en pixels de una cadena - [[nodiscard]] auto getCharacterSize() const -> int; // Devuelve el tamaño del caracter + [[nodiscard]] auto length(const std::string& text, int kerning = 1) const -> int; // Obtiene la longitud en pixels de una cadena + [[nodiscard]] auto getCharacterSize() const -> int; // Devuelve el tamaño del caracter + [[nodiscard]] auto glyphWidth(uint32_t codepoint, int kerning = 0) const -> int; // Devuelve el ancho en pixels de un glifo void setFixedWidth(bool value); // Establece si se usa un tamaño fijo de letra - static auto loadTextFile(const std::string& file_path) -> std::shared_ptr; // Método de utilidad para cargar ficheros de texto + static auto loadTextFile(const std::string& file_path) -> std::shared_ptr; // Carga un fichero de definición de fuente .fnt + static auto codepointToUtf8(uint32_t cp) -> std::string; // Convierte un codepoint Unicode a string UTF-8 + static auto nextCodepoint(const std::string& s, size_t& pos) -> uint32_t; // Extrae el siguiente codepoint UTF-8 private: // Objetos y punteros std::unique_ptr sprite_ = nullptr; // Objeto con los graficos para el texto // Variables - int box_width_ = 0; // Anchura de la caja de cada caracter en el png - int box_height_ = 0; // Altura de la caja de cada caracter en el png - bool fixed_width_ = false; // Indica si el texto se ha de escribir con longitud fija en todas las letras - std::array offset_{}; // Vector con las posiciones y ancho de cada letra -}; \ No newline at end of file + int box_width_ = 0; // Anchura de la caja de cada caracter en el png + int box_height_ = 0; // Altura de la caja de cada caracter en el png + bool fixed_width_ = false; // Indica si el texto se ha de escribir con longitud fija + std::unordered_map offset_; // Posición y ancho de cada glifo (clave: codepoint Unicode) +}; diff --git a/source/core/resources/resource_cache.cpp b/source/core/resources/resource_cache.cpp index e4a67c3..6b750ed 100644 --- a/source/core/resources/resource_cache.cpp +++ b/source/core/resources/resource_cache.cpp @@ -370,11 +370,11 @@ namespace Resource { std::cout << "\n>> CREATING TEXT_OBJECTS" << '\n'; std::vector resources = { - {.key = "aseprite", .texture_file = "aseprite.gif", .text_file = "aseprite.txt"}, - {.key = "gauntlet", .texture_file = "gauntlet.gif", .text_file = "gauntlet.txt"}, - {.key = "smb2", .texture_file = "smb2.gif", .text_file = "smb2.txt"}, - {.key = "subatomic", .texture_file = "subatomic.gif", .text_file = "subatomic.txt"}, - {.key = "8bithud", .texture_file = "8bithud.gif", .text_file = "8bithud.txt"}}; + {.key = "aseprite", .texture_file = "aseprite.gif", .text_file = "aseprite.fnt"}, + {.key = "gauntlet", .texture_file = "gauntlet.gif", .text_file = "gauntlet.fnt"}, + {.key = "smb2", .texture_file = "smb2.gif", .text_file = "smb2.fnt"}, + {.key = "subatomic", .texture_file = "subatomic.gif", .text_file = "subatomic.fnt"}, + {.key = "8bithud", .texture_file = "8bithud.gif", .text_file = "8bithud.fnt"}}; for (const auto& res_info : resources) { texts_.emplace_back(TextResource{.name = res_info.key, .text = std::make_shared(getSurface(res_info.texture_file), getTextFile(res_info.text_file))}); diff --git a/source/game/scenes/title.cpp b/source/game/scenes/title.cpp index 9d5c251..0f8272a 100644 --- a/source/game/scenes/title.cpp +++ b/source/game/scenes/title.cpp @@ -60,17 +60,21 @@ void Title::initMarquee() { letters_.clear(); long_text_ = Locale::get()->get("title.marquee"); - // Pre-calcular anchos de caracteres para eficiencia - for (size_t i = 0; i < long_text_.length(); ++i) { + // Pre-calcular anchos de caracteres para eficiencia (iteración por codepoints UTF-8) + size_t pos = 0; + while (pos < long_text_.size()) { + uint32_t cp = Text::nextCodepoint(long_text_, pos); Glyph l; - l.letter = long_text_[i]; // char directo, no substring - l.x = MARQUEE_START_X; // Usar constante - l.width = marquee_text_->length(std::string(1, long_text_[i])); // Pre-calcular ancho + l.codepoint = cp; + l.x = MARQUEE_START_X; + l.width = static_cast(marquee_text_->glyphWidth(cp, 1)); // Pre-calcular ancho con kerning l.enabled = false; letters_.push_back(l); } - letters_[0].enabled = true; + if (!letters_.empty()) { + letters_[0].enabled = true; + } first_active_letter_ = 0; last_active_letter_ = 0; } @@ -224,9 +228,9 @@ void Title::renderMarquee() { const auto& letter = letters_[i]; if (letter.enabled) { marquee_text_->writeColored( - static_cast(letter.x), // Conversión explícita float→int - static_cast(MARQUEE_Y), // Usar constante - std::string(1, letter.letter), // Convertir char a string + static_cast(letter.x), // Conversión explícita float→int + static_cast(MARQUEE_Y), // Usar constante + Text::codepointToUtf8(letter.codepoint), // Convertir codepoint a string UTF-8 static_cast(PaletteColor::MAGENTA)); } } diff --git a/source/game/scenes/title.hpp b/source/game/scenes/title.hpp index e48edce..ea04450 100644 --- a/source/game/scenes/title.hpp +++ b/source/game/scenes/title.hpp @@ -25,10 +25,10 @@ class Title { private: // --- Estructuras y enumeraciones --- struct Glyph { - char letter; // Letra a escribir (char es más eficiente que std::string) - float x; // Posición en el eje x (float para precisión con delta time) - float width; // Ancho pre-calculado del carácter - bool enabled; // Solo se escriben y mueven si estan habilitadas + uint32_t codepoint{0}; // Codepoint Unicode del carácter + float x{0.0F}; // Posición en el eje x (float para precisión con delta time) + float width{0.0F}; // Ancho pre-calculado del carácter + bool enabled{false}; // Solo se escriben y mueven si estan habilitadas }; enum class State {