#include "core/rendering/text.hpp" #include #include // Para size_t #include // Para cerr #include // Para istringstream #include // Para runtime_error #include "core/rendering/screen.hpp" // Para Screen #include "core/rendering/surface.hpp" // Para Surface #include "core/rendering/surface_sprite.hpp" // Para SSprite #include "core/resources/resource_helper.hpp" // Para ResourceHelper #include "utils/utils.hpp" // Para getFileName, stringToColor, printWithDots // 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 { // NOLINT(readability-convert-member-functions-to-static) 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 { // NOLINT(readability-convert-member-functions-to-static) 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 { // NOLINT(readability-convert-member-functions-to-static) auto tf = std::make_shared(); 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)); } std::string content(file_data.begin(), file_data.end()); std::istringstream stream(content); std::string line; int glyph_index = 0; while (std::getline(stream, line)) { if (!line.empty() && line.back() == '\r') { line.pop_back(); } if (line.empty() || line[0] == '#') { continue; } std::istringstream ls(line); std::string key; ls >> key; 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 if (key == "cell_spacing") { ls >> tf->cell_spacing; } else if (key == "row_spacing") { ls >> tf->row_spacing; } 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{}; const int ROW_SP = tf->row_spacing > 0 ? tf->row_spacing : tf->cell_spacing; off.x = ((glyph_index % tf->columns) * (tf->box_width + tf->cell_spacing)) + tf->cell_spacing; off.y = ((glyph_index / tf->columns) * (tf->box_height + ROW_SP)) + tf->cell_spacing; off.w = width; tf->offset[codepoint] = off; ++glyph_index; } } printWithDots("Text File : ", getFileName(file_path), "[ LOADED ]"); return tf; } // Constructor desde fichero Text::Text(const std::shared_ptr& surface, const std::string& text_file) { auto tf = loadTextFile(text_file); box_height_ = tf->box_height; box_width_ = tf->box_width; offset_ = tf->offset; sprite_ = std::make_unique(surface, SDL_FRect{.x = 0.0F, .y = 0.0F, .w = static_cast(box_width_), .h = static_cast(box_height_)}); } // Constructor desde estructura precargada Text::Text(const std::shared_ptr& surface, const std::shared_ptr& text_file) : sprite_(std::make_unique(surface, SDL_FRect{.x = 0.0F, .y = 0.0F, .w = static_cast(text_file->box_width), .h = static_cast(text_file->box_height)})), box_width_(text_file->box_width), box_height_(text_file->box_height), offset_(text_file->offset) { } // Escribe texto en pantalla void Text::write(int x, int y, const std::string& text, int kerning, int lenght) { // NOLINT(readability-convert-member-functions-to-static) int shift = 0; int glyphs_done = 0; size_t pos = 0; sprite_->setY(y); 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; } } // Escribe el texto en una surface auto Text::writeToSurface(const std::string& text, int zoom, int kerning) -> std::shared_ptr { // NOLINT(readability-make-member-function-const) auto width = length(text, kerning) * zoom; auto height = box_height_ * zoom; auto surface = std::make_shared(width, height); auto previuos_renderer = Screen::get()->getRendererSurface(); Screen::get()->setRendererSurface(surface); surface->clear(stringToColor("transparent")); write(0, 0, text, kerning); Screen::get()->setRendererSurface(previuos_renderer); return surface; } // Escribe el texto con extras en una surface auto Text::writeDXToSurface(Uint8 flags, const std::string& text, int kerning, Uint8 text_color, Uint8 shadow_distance, Uint8 shadow_color, int lenght) -> std::shared_ptr { // NOLINT(readability-make-member-function-const) auto width = Text::length(text, kerning) + shadow_distance; auto height = box_height_ + shadow_distance; auto surface = std::make_shared(width, height); auto previuos_renderer = Screen::get()->getRendererSurface(); Screen::get()->setRendererSurface(surface); surface->clear(stringToColor("transparent")); writeDX(flags, 0, 0, text, kerning, text_color, shadow_distance, shadow_color, lenght); Screen::get()->setRendererSurface(previuos_renderer); return surface; } // Escribe el texto con colores void Text::writeColored(int x, int y, const std::string& text, Uint8 color, int kerning, int lenght) { // NOLINT(readability-convert-member-functions-to-static) int shift = 0; int glyphs_done = 0; size_t pos = 0; sprite_->setY(y); 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; } } // Escribe el texto con sombra void Text::writeShadowed(int x, int y, const std::string& text, Uint8 color, Uint8 shadow_distance, int kerning, int lenght) { writeColored(x + shadow_distance, y + shadow_distance, text, color, kerning, lenght); write(x, y, text, kerning, lenght); } // Escribe el texto centrado en un punto x void Text::writeCentered(int x, int y, const std::string& text, int kerning, int lenght) { x -= (Text::length(text, kerning) / 2); write(x, y, text, kerning, lenght); } // Escribe texto con extras 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) { // NOLINT(readability-convert-member-functions-to-static) 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); if (CENTERED) { x -= (Text::length(text, kerning) / 2); } if (SHADOWED) { writeColored(x + shadow_distance, y + shadow_distance, text, shadow_color, kerning, lenght); } if (STROKED) { const int MAX_DIST = static_cast(shadow_distance); for (int dist = 1; dist <= MAX_DIST; ++dist) { for (int dy = -dist; dy <= dist; ++dy) { for (int dx = -dist; dx <= dist; ++dx) { writeColored(x + dx, y + dy, text, shadow_color, kerning, lenght); } } } } if (COLORED) { writeColored(x, y, text, text_color, kerning, lenght); } else { writeColored(x, y, text, text_color, kerning, lenght); } } // Obtiene la longitud en pixels de una cadena UTF-8 auto Text::length(const std::string& text, int kerning) const -> int { // NOLINT(readability-convert-member-functions-to-static) int shift = 0; 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; } } return shift > 0 ? shift - kerning : 0; } // Devuelve el ancho en pixels de un glifo dado su codepoint Unicode auto Text::glyphWidth(uint32_t codepoint, int kerning) const -> int { // NOLINT(readability-convert-member-functions-to-static) 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 clip rect (región en el bitmap) de un glifo dado su codepoint auto Text::getGlyphClip(uint32_t codepoint) const -> SDL_FRect { auto it = offset_.find(codepoint); if (it == offset_.end()) { it = offset_.find('?'); } if (it == offset_.end()) { return {.x = 0.0F, .y = 0.0F, .w = 0.0F, .h = 0.0F}; } return {.x = static_cast(it->second.x), .y = static_cast(it->second.y), .w = static_cast(box_width_), .h = static_cast(box_height_)}; } // Devuelve el tamaño de la caja de cada caracter auto Text::getCharacterSize() const -> int { return box_width_; } // Establece si se usa un tamaño fijo de letra void Text::setFixedWidth(bool value) { fixed_width_ = value; }