#include "core/rendering/text.hpp" #include #include #include #include #include #include #include #include #include "core/resources/resource_helper.hpp" // Forward declarations de gif.h (inclòs des de jdraw8.cpp, no es pot incloure dos vegades) struct rgb; // NOLINTNEXTLINE(readability-identifier-naming) — exportat per external/gif.h, no controlem el nom. extern auto LoadGif(unsigned char* data, unsigned short* w, unsigned short* h) -> unsigned char*; Text::Text(const char* fnt_file, const char* gif_file) { loadBitmap(gif_file); loadFont(fnt_file); } // --- UTF-8 --- auto Text::nextCodepoint(const char*& ptr) -> uint32_t { auto byte = static_cast(*ptr); if (byte == 0) { return 0; } uint32_t cp = 0; int extra = 0; if (byte < 0x80) { cp = byte; } else if ((byte & 0xE0) == 0xC0) { cp = byte & 0x1F; extra = 1; } else if ((byte & 0xF0) == 0xE0) { cp = byte & 0x0F; extra = 2; } else if ((byte & 0xF8) == 0xF0) { cp = byte & 0x07; extra = 3; } else { ptr++; return 0xFFFD; } ptr++; for (int i = 0; i < extra; i++) { auto cont = static_cast(*ptr); if ((cont & 0xC0) != 0x80) { return 0xFFFD; } cp = (cp << 6) | (cont & 0x3F); ptr++; } return cp; } // --- Càrrega de font --- void Text::loadFont(const char* fnt_file) { auto buffer = ResourceHelper::loadFile(fnt_file); if (buffer.empty()) { std::cerr << "Text: unable to load font file: " << fnt_file << '\n'; return; } std::istringstream stream(std::string(reinterpret_cast(buffer.data()), buffer.size())); std::string line; int glyph_index = 0; while (std::getline(stream, line)) { // Ignora comentaris i línies buides if (line.empty() || line[0] == '#') { continue; } // Elimina comentaris inline auto comment_pos = line.find('#'); if (comment_pos != std::string::npos) { line.resize(comment_pos); } // Parseja directives if (line.starts_with("box_width")) { sscanf(line.c_str(), "box_width %d", &box_width_); continue; } if (line.starts_with("box_height")) { sscanf(line.c_str(), "box_height %d", &box_height_); continue; } if (line.starts_with("columns")) { sscanf(line.c_str(), "columns %d", &columns_); continue; } if (line.starts_with("cell_spacing")) { sscanf(line.c_str(), "cell_spacing %d", &cell_spacing_); continue; } if (line.starts_with("row_spacing")) { sscanf(line.c_str(), "row_spacing %d", &row_spacing_); continue; } // Línies de glifos: "codepoint width" uint32_t codepoint = 0; int visual_width = 0; if (sscanf(line.c_str(), "%u %d", &codepoint, &visual_width) == 2) { int col = glyph_index % columns_; int row = glyph_index / columns_; GlyphInfo glyph{}; glyph.x = col * (box_width_ + cell_spacing_) + cell_spacing_; glyph.y = row * (box_height_ + (row_spacing_ > 0 ? row_spacing_ : cell_spacing_)) + cell_spacing_; glyph.w = visual_width; glyphs_[codepoint] = glyph; glyph_index++; } } std::cout << "Text: loaded font with " << glyphs_.size() << " glyphs (" << box_width_ << "x" << box_height_ << ")\n"; } void Text::loadBitmap(const char* gif_file) { auto buffer = ResourceHelper::loadFile(gif_file); if (buffer.empty()) { std::cerr << "Text: unable to load bitmap: " << gif_file << '\n'; return; } // Extrau dimensions del header GIF (bytes 6-7 = width, 8-9 = height, little-endian) auto* raw = buffer.data(); int w = raw[6] | (raw[7] << 8); int h = raw[8] | (raw[9] << 8); unsigned short gw = 0; unsigned short gh = 0; Uint8* pixels = LoadGif(raw, &gw, &gh); if (pixels == nullptr) { std::cerr << "Text: unable to decode GIF: " << gif_file << '\n'; return; } bitmap_width_ = w; bitmap_height_ = h; bitmap_.assign(pixels, pixels + (static_cast(w) * h)); free(pixels); // LoadGif usa malloc internament std::cout << "Text: bitmap loaded " << w << "x" << h << '\n'; } // --- Renderitzat --- auto Text::resolveGlyph(uint32_t cp) const -> const GlyphInfo* { auto it = glyphs_.find(cp); if (it != glyphs_.end()) { return &it->second; } it = glyphs_.find('?'); return (it != glyphs_.end()) ? &it->second : nullptr; } void Text::blitGlyph(Uint32* pixel_data, int dst_x, int dst_y, const GlyphInfo& glyph, Uint32 color, int clip_x_min, int clip_x_max, int clip_y_min, int clip_y_max) const { const int GY_START = std::max(0, clip_y_min - dst_y); const int GY_END = std::min(box_height_, clip_y_max - dst_y); const int GX_START = std::max(0, clip_x_min - dst_x); const int GX_END = std::min(glyph.w, clip_x_max - dst_x); for (int gy = GY_START; gy < GY_END; gy++) { const int SRC_Y = glyph.y + gy; if (SRC_Y >= bitmap_height_) { continue; } const int DST_ROW = dst_y + gy; for (int gx = GX_START; gx < GX_END; gx++) { const int SRC_X = glyph.x + gx; if (SRC_X >= bitmap_width_) { continue; } const Uint8 PIXEL = bitmap_[SRC_X + (SRC_Y * bitmap_width_)]; if (PIXEL != 0) { pixel_data[(dst_x + gx) + (DST_ROW * SCREEN_WIDTH)] = color; } } } } void Text::draw(Uint32* pixel_data, int x, int y, const char* text, Uint32 color) const { if (bitmap_.empty() || (pixel_data == nullptr)) { return; } const char* ptr = text; int cursor_x = x; while (*ptr != 0) { uint32_t cp = nextCodepoint(ptr); if (cp == 0) { break; } const GlyphInfo* glyph = resolveGlyph(cp); if (glyph == nullptr) { cursor_x += box_width_; continue; } blitGlyph(pixel_data, cursor_x, y, *glyph, color, 0, SCREEN_WIDTH, 0, SCREEN_HEIGHT); cursor_x += glyph->w + 1; } } void Text::drawCentered(Uint32* pixel_data, int y, const char* text, Uint32 color) const { int w = width(text); int x = (SCREEN_WIDTH - w) / 2; draw(pixel_data, x, y, text, color); } void Text::drawClipped(Uint32* pixel_data, int x, int y, const char* text, Uint32 color, int clip_x_min, int clip_x_max, int clip_y_min, int clip_y_max) const { if (bitmap_.empty() || (pixel_data == nullptr)) { return; } if (y + box_height_ <= clip_y_min || y >= clip_y_max) { return; } const int X_MIN = std::max(0, clip_x_min); const int X_MAX = std::min(SCREEN_WIDTH, clip_x_max); const int Y_MIN = std::max(0, clip_y_min); const int Y_MAX = std::min(SCREEN_HEIGHT, clip_y_max); const char* ptr = text; int cursor_x = x; while (*ptr != 0) { uint32_t cp = nextCodepoint(ptr); if (cp == 0) { break; } const GlyphInfo* glyph = resolveGlyph(cp); if (glyph == nullptr) { cursor_x += box_width_; continue; } if (cursor_x + glyph->w > X_MIN && cursor_x < X_MAX) { blitGlyph(pixel_data, cursor_x, y, *glyph, color, X_MIN, X_MAX, Y_MIN, Y_MAX); } cursor_x += glyph->w + 1; } } void Text::drawMono(Uint32* pixel_data, int x, int y, const char* text, Uint32 color, int cell_w) const { if (bitmap_.empty() || (pixel_data == nullptr)) { return; } const char* ptr = text; int cursor_x = x; while (*ptr != 0) { uint32_t cp = nextCodepoint(ptr); if (cp == 0) { break; } const GlyphInfo* glyph = resolveGlyph(cp); if (glyph == nullptr) { cursor_x += cell_w; continue; } const int GLYPH_X = cursor_x + ((cell_w - glyph->w) / 2); blitGlyph(pixel_data, GLYPH_X, y, *glyph, color, 0, SCREEN_WIDTH, 0, SCREEN_HEIGHT); cursor_x += cell_w; } } void Text::drawMonoDigits(Uint32* pixel_data, int x, int y, const char* text, Uint32 color, int digit_cell_w) const { if (bitmap_.empty() || (pixel_data == nullptr)) { return; } const char* ptr = text; int cursor_x = x; bool first = true; while (*ptr != 0) { uint32_t cp = nextCodepoint(ptr); if (cp == 0) { break; } if (!first) { cursor_x += 1; // kerning } const GlyphInfo* glyph = resolveGlyph(cp); if (glyph == nullptr) { cursor_x += box_width_; first = false; continue; } const bool IS_DIGIT = (cp >= '0' && cp <= '9'); const int GLYPH_X = IS_DIGIT ? cursor_x + ((digit_cell_w - glyph->w) / 2) : cursor_x; blitGlyph(pixel_data, GLYPH_X, y, *glyph, color, 0, SCREEN_WIDTH, 0, SCREEN_HEIGHT); cursor_x += IS_DIGIT ? digit_cell_w : glyph->w; first = false; } } auto Text::widthMonoDigits(const char* text, int digit_cell_w) const -> int { const char* ptr = text; int w = 0; bool first = true; while (*ptr != 0) { uint32_t cp = nextCodepoint(ptr); if (cp == 0) { break; } if (!first) { w += 1; // kerning } first = false; bool is_digit = (cp >= '0' && cp <= '9'); if (is_digit) { w += digit_cell_w; } else { auto it = glyphs_.find(cp); if (it == glyphs_.end()) { it = glyphs_.find('?'); } if (it != glyphs_.end()) { w += it->second.w; } else { w += box_width_; } } } return w; } auto Text::widthMono(const char* text, int cell_w) -> int { const char* ptr = text; int count = 0; while (*ptr != 0) { uint32_t cp = nextCodepoint(ptr); if (cp == 0) { break; } count++; } return count * cell_w; } auto Text::width(const char* text) const -> int { const char* ptr = text; int w = 0; bool first = true; while (*ptr != 0) { uint32_t cp = nextCodepoint(ptr); if (cp == 0) { break; } auto it = glyphs_.find(cp); if (it == glyphs_.end()) { it = glyphs_.find('?'); } if (!first) { w += 1; // kerning } first = false; if (it != glyphs_.end()) { w += it->second.w; } else { w += box_width_; } } return w; }