#include "core/rendering/text.h" #include // for cout #include #include "core/rendering/sprite.h" // for Sprite #include "core/rendering/texture.h" // for Texture #include "core/resources/resource_helper.h" // for loadFile (pack + filesystem fallback) #include "utils/utils.h" // for Color // Parser compartido: rellena un TextFile desde cualquier istream static void parseTextFileStream(std::istream &rfile, TextFile &tf) { std::string buffer; std::getline(rfile, buffer); std::getline(rfile, buffer); tf.box_width = std::stoi(buffer); std::getline(rfile, buffer); std::getline(rfile, buffer); tf.box_height = std::stoi(buffer); int index = 32; int line_read = 0; while (std::getline(rfile, buffer)) { if (line_read % 2 == 1) { tf.offset[index++].w = std::stoi(buffer); } buffer.clear(); line_read++; } } static void computeTextFileOffsets(TextFile &tf) { 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; } } // Llena una estructuta TextFile desde un fichero (vía ResourceHelper: pack o filesystem) auto loadTextFile(const std::string &file, bool verbose) -> TextFile { const std::string FILE_NAME = file.substr(file.find_last_of("\\/") + 1); auto bytes = ResourceHelper::loadFile(file); if (bytes.empty()) { if (verbose) { std::cout << "Warning: Unable to open " << FILE_NAME.c_str() << " file" << '\n'; } TextFile tf; tf.box_width = 0; tf.box_height = 0; for (auto &i : tf.offset) { i.x = 0; i.y = 0; i.w = 0; } computeTextFileOffsets(tf); return tf; } if (verbose) { std::cout << "Text loaded: " << FILE_NAME.c_str() << '\n'; } return loadTextFileFromMemory(bytes, verbose); } // Llena una estructura TextFile desde bytes en memoria auto loadTextFileFromMemory(const std::vector &bytes, bool verbose) -> TextFile { TextFile tf; tf.box_width = 0; tf.box_height = 0; for (auto &i : tf.offset) { i.x = 0; i.y = 0; i.w = 0; } if (!bytes.empty()) { std::string content(reinterpret_cast(bytes.data()), bytes.size()); std::stringstream ss(content); parseTextFileStream(ss, tf); if (verbose) { std::cout << "Text loaded from memory" << '\n'; } } computeTextFileOffsets(tf); return tf; } // Constructor Text::Text(const std::string &bitmap_file, const std::string &text_file, SDL_Renderer *renderer) { // Carga los offsets desde el fichero TextFile tf = loadTextFile(text_file); // Inicializa variables desde la estructura box_height_ = tf.box_height; box_width_ = tf.box_width; for (int i = 0; i < 128; ++i) { offset_[i].x = tf.offset[i].x; offset_[i].y = tf.offset[i].y; offset_[i].w = tf.offset[i].w; } // Crea los objetos texture_ = new Texture(renderer, bitmap_file); sprite_ = new Sprite({0, 0, box_width_, box_height_}, texture_, renderer); // Inicializa variables fixed_width_ = false; } // Constructor Text::Text(const std::string &text_file, Texture *texture, SDL_Renderer *renderer) { // Carga los offsets desde el fichero TextFile tf = loadTextFile(text_file); // Inicializa variables desde la estructura box_height_ = tf.box_height; box_width_ = tf.box_width; for (int i = 0; i < 128; ++i) { offset_[i].x = tf.offset[i].x; offset_[i].y = tf.offset[i].y; offset_[i].w = tf.offset[i].w; } // Crea los objetos this->texture_ = nullptr; sprite_ = new Sprite({0, 0, box_width_, box_height_}, texture, renderer); // Inicializa variables fixed_width_ = false; } // Constructor Text::Text(TextFile *text_file, Texture *texture, SDL_Renderer *renderer) { // Inicializa variables desde la estructura box_height_ = text_file->box_height; box_width_ = text_file->box_width; for (int i = 0; i < 128; ++i) { offset_[i].x = text_file->offset[i].x; offset_[i].y = text_file->offset[i].y; offset_[i].w = text_file->offset[i].w; } // Crea los objetos this->texture_ = nullptr; sprite_ = new Sprite({0, 0, box_width_, box_height_}, texture, renderer); // Inicializa variables fixed_width_ = false; } // Constructor desde bytes Text::Text(const std::vector &png_bytes, const std::vector &txt_bytes, SDL_Renderer *renderer) { TextFile tf = loadTextFileFromMemory(txt_bytes); box_height_ = tf.box_height; box_width_ = tf.box_width; for (int i = 0; i < 128; ++i) { offset_[i].x = tf.offset[i].x; offset_[i].y = tf.offset[i].y; offset_[i].w = tf.offset[i].w; } // Crea la textura desde bytes (Text es dueño en este overload) texture_ = new Texture(renderer, png_bytes); sprite_ = new Sprite({0, 0, box_width_, box_height_}, texture_, renderer); fixed_width_ = false; } // Destructor Text::~Text() { delete sprite_; delete texture_; } // 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(); } sprite_->setPosY(y); const int WIDTH = sprite_->getWidth(); const int HEIGHT = sprite_->getHeight(); for (int i = 0; i < lenght; ++i) { const int INDEX = static_cast(text[i]); sprite_->setSpriteClip(offset_[INDEX].x, offset_[INDEX].y, WIDTH, HEIGHT); sprite_->setPosX(x + shift); sprite_->render(); shift += fixed_width_ ? box_width_ : (offset_[INDEX].w + kerning); } } // Escribe el texto con colores void Text::writeColored(int x, int y, const std::string &text, Color color, int kerning, int lenght) { sprite_->getTexture()->setColor(color.r, color.g, color.b); write(x, y, text, kerning, lenght); sprite_->getTexture()->setColor(255, 255, 255); } // Escribe el texto con sombra void Text::writeShadowed(int x, int y, const std::string &text, Color color, Uint8 shadow_distance, int kerning, int lenght) { sprite_->getTexture()->setColor(color.r, color.g, color.b); write(x + shadow_distance, y + shadow_distance, text, kerning, lenght); sprite_->getTexture()->setColor(255, 255, 255); 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::lenght(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, Color text_color, Uint8 shadow_distance, Color shadow_color, int lenght) { const bool CENTERED = ((flags & TXT_CENTER) == TXT_CENTER); const bool SHADOWED = ((flags & TXT_SHADOW) == TXT_SHADOW); const bool COLORED = ((flags & TXT_COLOR) == TXT_COLOR); const bool STROKED = ((flags & TXT_STROKE) == TXT_STROKE); if (CENTERED) { x -= (Text::lenght(text, kerning) / 2); } if (SHADOWED) { writeColored(x + shadow_distance, y + shadow_distance, text, shadow_color, kerning, lenght); } if (STROKED) { for (int dist = 1; dist <= shadow_distance; ++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 { write(x, y, text, kerning, lenght); } } // Obtiene la longitud en pixels de una cadena auto Text::lenght(const std::string &text, int kerning) -> int { int shift = 0; for (int i = 0; i < (int)text.length(); ++i) { shift += (offset_[static_cast(text[i])].w + kerning); } // Descuenta el kerning del último caracter return shift - kerning; } // Devuelve el valor de la variable auto Text::getCharacterSize() const -> int { return box_width_; } // Recarga la textura void Text::reLoadTexture() { sprite_->getTexture()->reLoad(); } // Establece si se usa un tamaño fijo de letra void Text::setFixedWidth(bool value) { fixed_width_ = value; }