#include "textrenderer.hpp" #include #include #include "resource_manager.hpp" TextRenderer::TextRenderer() : renderer_(nullptr), font_(nullptr), font_size_(0), use_antialiasing_(true), font_data_buffer_(nullptr) { } TextRenderer::~TextRenderer() { cleanup(); } bool TextRenderer::init(SDL_Renderer* renderer, const char* font_path, int font_size, bool use_antialiasing) { renderer_ = renderer; font_size_ = font_size; use_antialiasing_ = use_antialiasing; font_path_ = font_path; // Guardar ruta para reinitialize() // Inicializar SDL_ttf si no está inicializado if (!TTF_WasInit()) { if (!TTF_Init()) { SDL_Log("Error al inicializar SDL_ttf: %s", SDL_GetError()); return false; } } // Intentar cargar la fuente desde ResourceManager (pack o disco) unsigned char* fontData = nullptr; size_t fontDataSize = 0; if (ResourceManager::loadResource(font_path, fontData, fontDataSize)) { // Crear SDL_IOStream desde memoria SDL_IOStream* fontIO = SDL_IOFromConstMem(fontData, static_cast(fontDataSize)); if (fontIO != nullptr) { // Cargar fuente desde IOStream font_ = TTF_OpenFontIO(fontIO, true, font_size); // true = cerrar stream automáticamente if (font_ == nullptr) { SDL_Log("Error al cargar fuente desde memoria '%s': %s", font_path, SDL_GetError()); delete[] fontData; // Liberar solo si falla la carga return false; } // CRÍTICO: NO eliminar fontData aquí - SDL_ttf necesita estos datos en memoria // mientras la fuente esté abierta. Se liberará en cleanup() font_data_buffer_ = fontData; SDL_Log("Fuente cargada desde ResourceManager: %s (%lu bytes)", font_path, (unsigned long)fontDataSize); return true; } else { delete[] fontData; } } // Fallback final: intentar cargar directamente desde disco (por si falla ResourceManager) font_ = TTF_OpenFont(font_path, font_size); if (font_ == nullptr) { SDL_Log("Error al cargar fuente '%s': %s", font_path, SDL_GetError()); return false; } return true; } bool TextRenderer::reinitialize(int new_font_size) { // Verificar que tenemos todo lo necesario if (renderer_ == nullptr || font_path_.empty()) { SDL_Log("Error: TextRenderer no inicializado correctamente para reinitialize()"); return false; } // Si el tamaño es el mismo, no hacer nada if (new_font_size == font_size_) { return true; } // Cerrar fuente actual y liberar buffer previo if (font_ != nullptr) { TTF_CloseFont(font_); font_ = nullptr; } if (font_data_buffer_ != nullptr) { delete[] font_data_buffer_; font_data_buffer_ = nullptr; } // Intentar cargar la fuente desde ResourceManager con el nuevo tamaño unsigned char* fontData = nullptr; size_t fontDataSize = 0; if (ResourceManager::loadResource(font_path_, fontData, fontDataSize)) { SDL_IOStream* fontIO = SDL_IOFromConstMem(fontData, static_cast(fontDataSize)); if (fontIO != nullptr) { font_ = TTF_OpenFontIO(fontIO, true, new_font_size); if (font_ == nullptr) { SDL_Log("Error al recargar fuente '%s' con tamaño %d: %s", font_path_.c_str(), new_font_size, SDL_GetError()); delete[] fontData; // Liberar solo si falla return false; } // Mantener buffer en memoria (NO eliminar) font_data_buffer_ = fontData; font_size_ = new_font_size; SDL_Log("Fuente recargada desde ResourceManager: %s (tamaño %d)", font_path_.c_str(), new_font_size); return true; } else { delete[] fontData; } } // Fallback: cargar directamente desde disco font_ = TTF_OpenFont(font_path_.c_str(), new_font_size); if (font_ == nullptr) { SDL_Log("Error al recargar fuente '%s' con tamaño %d: %s", font_path_.c_str(), new_font_size, SDL_GetError()); return false; } font_size_ = new_font_size; return true; } void TextRenderer::cleanup() { if (font_ != nullptr) { TTF_CloseFont(font_); font_ = nullptr; } if (font_data_buffer_ != nullptr) { delete[] font_data_buffer_; font_data_buffer_ = nullptr; } renderer_ = nullptr; } void TextRenderer::print(int x, int y, const char* text, uint8_t r, uint8_t g, uint8_t b) { if (!isInitialized() || text == nullptr || text[0] == '\0') { return; } // Crear superficie con el texto renderizado SDL_Color color = {r, g, b, 255}; SDL_Surface* text_surface = nullptr; if (use_antialiasing_) { // Con antialiasing (suave, mejor calidad) text_surface = TTF_RenderText_Blended(font_, text, strlen(text), color); } else { // Sin antialiasing (píxeles nítidos, estilo retro) text_surface = TTF_RenderText_Solid(font_, text, strlen(text), color); } if (text_surface == nullptr) { SDL_Log("Error al renderizar texto: %s", SDL_GetError()); return; } // Crear textura desde la superficie SDL_Texture* text_texture = SDL_CreateTextureFromSurface(renderer_, text_surface); if (text_texture == nullptr) { SDL_Log("Error al crear textura: %s", SDL_GetError()); SDL_DestroySurface(text_surface); return; } // Preparar rectángulo de destino SDL_FRect dest_rect; dest_rect.x = static_cast(x); dest_rect.y = static_cast(y); dest_rect.w = static_cast(text_surface->w); dest_rect.h = static_cast(text_surface->h); // Renderizar la textura SDL_RenderTexture(renderer_, text_texture, nullptr, &dest_rect); // Limpiar recursos SDL_DestroyTexture(text_texture); SDL_DestroySurface(text_surface); } void TextRenderer::print(int x, int y, const std::string& text, uint8_t r, uint8_t g, uint8_t b) { print(x, y, text.c_str(), r, g, b); } void TextRenderer::printPhysical(int logical_x, int logical_y, const char* text, uint8_t r, uint8_t g, uint8_t b, float scale_x, float scale_y) { if (!isInitialized() || text == nullptr || text[0] == '\0') { return; } // Convertir coordenadas lógicas a físicas int physical_x = static_cast(logical_x * scale_x); int physical_y = static_cast(logical_y * scale_y); // Crear superficie con el texto renderizado SDL_Color color = {r, g, b, 255}; SDL_Surface* text_surface = nullptr; if (use_antialiasing_) { text_surface = TTF_RenderText_Blended(font_, text, strlen(text), color); } else { text_surface = TTF_RenderText_Solid(font_, text, strlen(text), color); } if (text_surface == nullptr) { SDL_Log("Error al renderizar texto: %s", SDL_GetError()); return; } // Crear textura desde la superficie SDL_Texture* text_texture = SDL_CreateTextureFromSurface(renderer_, text_surface); if (text_texture == nullptr) { SDL_Log("Error al crear textura: %s", SDL_GetError()); SDL_DestroySurface(text_surface); return; } // Renderizar en coordenadas físicas (bypass presentación lógica) // Usar SDL_RenderTexture con coordenadas absolutas de ventana SDL_FRect dest_rect; dest_rect.x = static_cast(physical_x); dest_rect.y = static_cast(physical_y); dest_rect.w = static_cast(text_surface->w); dest_rect.h = static_cast(text_surface->h); // Deshabilitar temporalmente presentación lógica para renderizar en píxeles físicos int logical_w = 0, logical_h = 0; SDL_RendererLogicalPresentation presentation_mode; SDL_GetRenderLogicalPresentation(renderer_, &logical_w, &logical_h, &presentation_mode); // Renderizar sin presentación lógica (coordenadas absolutas) SDL_SetRenderLogicalPresentation(renderer_, 0, 0, SDL_LOGICAL_PRESENTATION_DISABLED); SDL_RenderTexture(renderer_, text_texture, nullptr, &dest_rect); // Restaurar presentación lógica SDL_SetRenderLogicalPresentation(renderer_, logical_w, logical_h, presentation_mode); // Limpiar recursos SDL_DestroyTexture(text_texture); SDL_DestroySurface(text_surface); } void TextRenderer::printPhysical(int logical_x, int logical_y, const std::string& text, uint8_t r, uint8_t g, uint8_t b, float scale_x, float scale_y) { printPhysical(logical_x, logical_y, text.c_str(), r, g, b, scale_x, scale_y); } void TextRenderer::printAbsolute(int physical_x, int physical_y, const char* text, SDL_Color color) { if (!isInitialized() || text == nullptr || text[0] == '\0') { return; } // Crear superficie con el texto renderizado SDL_Surface* text_surface = nullptr; if (use_antialiasing_) { text_surface = TTF_RenderText_Blended(font_, text, strlen(text), color); } else { text_surface = TTF_RenderText_Solid(font_, text, strlen(text), color); } if (text_surface == nullptr) { SDL_Log("Error al renderizar texto: %s", SDL_GetError()); return; } // Crear textura desde la superficie SDL_Texture* text_texture = SDL_CreateTextureFromSurface(renderer_, text_surface); if (text_texture == nullptr) { SDL_Log("Error al crear textura: %s", SDL_GetError()); SDL_DestroySurface(text_surface); return; } // Configurar alpha blending si el color tiene transparencia if (color.a < 255) { SDL_SetTextureBlendMode(text_texture, SDL_BLENDMODE_BLEND); SDL_SetTextureAlphaModFloat(text_texture, color.a / 255.0f); } // Obtener viewport ANTES de deshabilitar presentación lógica // En modo letterbox (F3), SDL crea un viewport con offset para centrar la imagen SDL_Rect viewport; SDL_GetRenderViewport(renderer_, &viewport); // Preparar rectángulo de destino en coordenadas físicas absolutas // Aplicar offset del viewport para que el texto se pinte dentro del área visible SDL_FRect dest_rect; dest_rect.x = static_cast(physical_x + viewport.x); dest_rect.y = static_cast(physical_y + viewport.y); dest_rect.w = static_cast(text_surface->w); dest_rect.h = static_cast(text_surface->h); // Deshabilitar temporalmente presentación lógica para renderizar en píxeles físicos int logical_w = 0, logical_h = 0; SDL_RendererLogicalPresentation presentation_mode; SDL_GetRenderLogicalPresentation(renderer_, &logical_w, &logical_h, &presentation_mode); // Renderizar sin presentación lógica (coordenadas absolutas con offset de viewport) SDL_SetRenderLogicalPresentation(renderer_, 0, 0, SDL_LOGICAL_PRESENTATION_DISABLED); SDL_RenderTexture(renderer_, text_texture, nullptr, &dest_rect); // Restaurar presentación lógica SDL_SetRenderLogicalPresentation(renderer_, logical_w, logical_h, presentation_mode); // Limpiar recursos SDL_DestroyTexture(text_texture); SDL_DestroySurface(text_surface); } void TextRenderer::printAbsolute(int physical_x, int physical_y, const std::string& text, SDL_Color color) { printAbsolute(physical_x, physical_y, text.c_str(), color); } int TextRenderer::getTextWidth(const char* text) { if (!isInitialized() || text == nullptr) { return 0; } int width = 0; int height = 0; if (!TTF_GetStringSize(font_, text, strlen(text), &width, &height)) { return 0; } return width; } int TextRenderer::getTextWidthPhysical(const char* text) { // Retorna el ancho REAL en píxeles físicos (sin escalado lógico) // Idéntico a getTextWidth() pero semánticamente diferente: // - Este método se usa cuando se necesita el ancho REAL de la fuente // - Útil para calcular dimensiones de UI en coordenadas físicas absolutas if (!isInitialized() || text == nullptr) { return 0; } int width = 0; int height = 0; if (!TTF_GetStringSize(font_, text, strlen(text), &width, &height)) { return 0; } return width; // Ancho real de la textura generada por TTF } int TextRenderer::getTextHeight() { if (!isInitialized()) { return 0; } return TTF_GetFontHeight(font_); }