#include "core/rendering/text.hpp" #include #include #include #include #include #include #include #include "core/jail/jfile.hpp" // Forward declarations de gif.h (inclòs des de jdraw8.cpp, no es pot incloure dos vegades) struct rgb; extern unsigned char* LoadGif(unsigned char* data, unsigned short* w, unsigned short* h); Text::Text(const char* fnt_file, const char* gif_file) { loadBitmap(gif_file); loadFont(fnt_file); } Text::~Text() { if (bitmap_) free(bitmap_); } // --- 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) { int filesize = 0; char* buffer = file_getfilebuffer(fnt_file, filesize, true); if (!buffer) { std::cerr << "Text: unable to load font file: " << fnt_file << '\n'; return; } std::istringstream stream(std::string(buffer, filesize)); free(buffer); 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 = line.substr(0, comment_pos); } // Parseja directives if (line.find("box_width") == 0) { sscanf(line.c_str(), "box_width %d", &box_width_); continue; } if (line.find("box_height") == 0) { sscanf(line.c_str(), "box_height %d", &box_height_); continue; } if (line.find("columns") == 0) { sscanf(line.c_str(), "columns %d", &columns_); continue; } if (line.find("cell_spacing") == 0) { sscanf(line.c_str(), "cell_spacing %d", &cell_spacing_); continue; } if (line.find("row_spacing") == 0) { 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) { int filesize = 0; char* buffer = file_getfilebuffer(gif_file, filesize); if (!buffer) { 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 = reinterpret_cast(buffer); int w = raw[6] | (raw[7] << 8); int h = raw[8] | (raw[9] << 8); unsigned short gw = 0, gh = 0; Uint8* pixels = LoadGif(raw, &gw, &gh); if (!pixels) { std::cerr << "Text: unable to decode GIF: " << gif_file << '\n'; free(buffer); return; } bitmap_width_ = w; bitmap_height_ = h; bitmap_ = pixels; free(buffer); std::cout << "Text: bitmap loaded " << w << "x" << h << '\n'; } // --- Renderitzat --- void Text::draw(Uint32* pixel_data, int x, int y, const char* text, Uint32 color) const { if (!bitmap_ || !pixel_data) return; const char* ptr = text; int cursor_x = x; while (*ptr) { uint32_t cp = nextCodepoint(ptr); if (cp == 0) break; auto it = glyphs_.find(cp); if (it == glyphs_.end()) { it = glyphs_.find('?'); if (it == glyphs_.end()) { cursor_x += box_width_; continue; } } const auto& glyph = it->second; // Pinta glifo pixel a pixel for (int gy = 0; gy < box_height_; gy++) { int dst_y = y + gy; if (dst_y < 0 || dst_y >= SCREEN_HEIGHT) continue; for (int gx = 0; gx < glyph.w; gx++) { int dst_x = cursor_x + gx; if (dst_x < 0 || dst_x >= SCREEN_WIDTH) continue; int src_x = glyph.x + gx; int src_y = glyph.y + gy; if (src_x >= bitmap_width_ || src_y >= bitmap_height_) continue; Uint8 pixel = bitmap_[src_x + src_y * bitmap_width_]; // Píxel no transparent (índex 0 és fons típicament) if (pixel != 0) { pixel_data[dst_x + dst_y * SCREEN_WIDTH] = color; } } } cursor_x += glyph.w + 1; // +1 kerning } } 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_ || !pixel_data) return; // Descart ràpid si el glifo sencer cau fora verticalment if (y + box_height_ <= clip_y_min || y >= clip_y_max) return; const char* ptr = text; int cursor_x = x; while (*ptr) { uint32_t cp = nextCodepoint(ptr); if (cp == 0) break; auto it = glyphs_.find(cp); if (it == glyphs_.end()) { it = glyphs_.find('?'); if (it == glyphs_.end()) { cursor_x += box_width_; continue; } } const auto& glyph = it->second; // Si el glifo està completament fora del clip horitzontal, salta if (cursor_x + glyph.w <= clip_x_min || cursor_x >= clip_x_max) { cursor_x += glyph.w + 1; continue; } for (int gy = 0; gy < box_height_; gy++) { int dst_y = y + gy; if (dst_y < 0 || dst_y >= SCREEN_HEIGHT) continue; if (dst_y < clip_y_min || dst_y >= clip_y_max) continue; for (int gx = 0; gx < glyph.w; gx++) { int dst_x = cursor_x + gx; if (dst_x < 0 || dst_x >= SCREEN_WIDTH) continue; if (dst_x < clip_x_min || dst_x >= clip_x_max) continue; int src_x = glyph.x + gx; int src_y = glyph.y + gy; if (src_x >= bitmap_width_ || src_y >= bitmap_height_) continue; Uint8 pixel = bitmap_[src_x + src_y * bitmap_width_]; if (pixel != 0) { pixel_data[dst_x + dst_y * SCREEN_WIDTH] = color; } } } cursor_x += glyph.w + 1; } } auto Text::width(const char* text) const -> int { const char* ptr = text; int w = 0; bool first = true; while (*ptr) { 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; }