#include "core/rendering/text.hpp" #include #include #include #include #include #include #include "core/rendering/sprite/sprite.hpp" #include "core/rendering/surface.hpp" #include "core/resources/resource_helper.hpp" #include "utils/utils.hpp" // Decodifica el pròxim codepoint UTF-8 de la cadena i avança 'pos' al byte // següent. Retorna U+FFFD davant seqüències malformades perquè el caller // seguisca dibuixant (cau al glif '?' més endavant). auto Text::nextCodepoint(const std::string& s, std::size_t& pos) -> std::uint32_t { auto c = static_cast(s[pos]); std::uint32_t cp = 0; std::size_t extra = 0; if (c < 0x80) { cp = c; extra = 0; } else if (c < 0xC0 || c >= 0xF8) { pos++; return 0xFFFD; } else if (c < 0xE0) { cp = c & 0x1F; extra = 1; } else if (c < 0xF0) { cp = c & 0x0F; extra = 2; } else { cp = c & 0x07; extra = 3; } pos++; for (std::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; } // Format .fnt: claus "box_width/box_height/columns/cell_spacing/row_spacing" // i una línia per glif amb "codepoint_decimal amplada_visual". Els glifs // s'ordenen al png per ordre d'aparició al .fnt (glyph_index). auto Text::loadTextFile(const std::string& file_path) -> std::shared_ptr { auto tf = std::make_shared(); auto file_data = Resource::Helper::loadFile(file_path); if (file_data.empty()) { std::cerr << "Error: Fitxer no trobat " << getFileName(file_path) << '\n'; throw std::runtime_error("Fitxer no trobat: " + 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 { std::uint32_t codepoint = 0; int width = 0; try { codepoint = static_cast(std::stoul(key)); ls >> width; } catch (...) { continue; } 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; } 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_)}); } 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) { } // Dibuixa la cadena amb la tinta per defecte. Els glifs amb codepoint no // registrat es substitueixen pel glif '?'. void Text::write(int x, int y, const std::string& text, int kerning, int length) { writeColored(x, y, text, DEFAULT_TEXT_COLOR, kerning, length); } void Text::writeColored(int x, int y, const std::string& text, Uint8 color, int kerning, int length) { int shift = 0; int glyphs_done = 0; std::size_t pos = 0; sprite_->setY(static_cast(y)); while (pos < text.size()) { if (length != -1 && glyphs_done >= length) { break; } std::uint32_t cp = nextCodepoint(text, pos); if (const Offset* off = resolveGlyph(cp); off != nullptr) { sprite_->setClip(static_cast(off->x), static_cast(off->y), static_cast(box_width_), static_cast(box_height_)); sprite_->setX(static_cast(x + shift)); sprite_->render(INK_COLOR_INDEX, color); shift += off->w + kerning; } ++glyphs_done; } } 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 length) { const bool CENTERED = ((flags & CENTER_FLAG) == CENTER_FLAG); const bool SHADOWED = ((flags & SHADOW_FLAG) == SHADOW_FLAG); const bool COLORED = ((flags & COLOR_FLAG) == COLOR_FLAG); const bool STROKED = ((flags & STROKE_FLAG) == STROKE_FLAG); const bool STROKED_4 = ((flags & STROKE4_FLAG) == STROKE4_FLAG); if (CENTERED) { x -= (Text::length(text, kerning) / 2); } if (SHADOWED) { writeColored(x + shadow_distance, y + shadow_distance, text, shadow_color, kerning, length); } if (STROKED_4) { const int MAX_DIST = static_cast(shadow_distance); for (int dist = 1; dist <= MAX_DIST; ++dist) { writeColored(x + dist, y, text, shadow_color, kerning, length); writeColored(x - dist, y, text, shadow_color, kerning, length); writeColored(x, y + dist, text, shadow_color, kerning, length); writeColored(x, y - dist, text, shadow_color, kerning, length); } } 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, length); } } } } if (COLORED) { writeColored(x, y, text, text_color, kerning, length); } else { write(x, y, text, kerning, length); } } // Amplada fixa per dígits: cada '0'-'9' ocupa digit_cell_w centrat dins de // la cel·la; la resta de glifs avancen amb la seua amplada natural. Emprat // pel comptador de temps de l'overlay RenderInfo per a que els dígits no // balleteigen quan canvien. void Text::writeMonoDigits(int x, int y, const std::string& text, Uint8 color, int digit_cell_w, int kerning) { int shift = 0; std::size_t pos = 0; sprite_->setY(static_cast(y)); while (pos < text.size()) { std::uint32_t cp = nextCodepoint(text, pos); const Offset* off = resolveGlyph(cp); if (off == nullptr) { continue; } const bool IS_DIGIT = (cp >= '0' && cp <= '9'); const int GLYPH_W = off->w; const int CELL_W = IS_DIGIT ? digit_cell_w : GLYPH_W; const int GLYPH_X = IS_DIGIT ? x + shift + ((CELL_W - GLYPH_W) / 2) : x + shift; sprite_->setClip(static_cast(off->x), static_cast(off->y), static_cast(box_width_), static_cast(box_height_)); sprite_->setX(static_cast(GLYPH_X)); sprite_->render(INK_COLOR_INDEX, color); shift += CELL_W + kerning; } } auto Text::lengthMonoDigits(const std::string& text, int digit_cell_w, int kerning) const -> int { int shift = 0; std::size_t pos = 0; while (pos < text.size()) { std::uint32_t cp = nextCodepoint(text, pos); const Offset* off = resolveGlyph(cp); if (off == nullptr) { continue; } const bool IS_DIGIT = (cp >= '0' && cp <= '9'); shift += (IS_DIGIT ? digit_cell_w : off->w) + kerning; } return shift > 0 ? shift - kerning : 0; } auto Text::length(const std::string& text, int kerning) const -> int { int shift = 0; std::size_t pos = 0; while (pos < text.size()) { std::uint32_t cp = nextCodepoint(text, pos); if (const Offset* off = resolveGlyph(cp); off != nullptr) { shift += off->w + kerning; } } return shift > 0 ? shift - kerning : 0; } auto Text::resolveGlyph(std::uint32_t cp) const -> const Offset* { auto it = offset_.find(cp); if (it == offset_.end()) { it = offset_.find('?'); } return (it != offset_.end()) ? &it->second : nullptr; }