442 lines
17 KiB
C++
442 lines
17 KiB
C++
#include "text.h"
|
|
|
|
#include <SDL3/SDL.h> // Para Uint8, SDL_GetRenderTarget, SDL_RenderClear, SDL_SetRenderDrawColor, SDL_SetRenderTarget, SDL_FRect, SDL_BLENDMODE_BLEND, SDL_PixelFormat, SDL_TextureAccess
|
|
|
|
#include <fstream> // Para basic_ifstream, basic_istream, basic_ostream, operator<<, endl, ifstream
|
|
#include <iostream> // Para cerr
|
|
#include <sstream> // Para istringstream
|
|
#include <stdexcept> // Para runtime_error
|
|
#include <string_view> // Para string_view
|
|
|
|
#include "color.h" // Para Color
|
|
#include "screen.h" // Para Screen
|
|
#include "sprite.h" // Para Sprite
|
|
#include "texture.h" // Para Texture
|
|
#include "utils.h" // Para getFileName, printWithDots
|
|
#include "resource_helper.h" // Para ResourceHelper
|
|
|
|
// Constructor
|
|
Text::Text(const std::shared_ptr<Texture> &texture, const std::string &text_file) {
|
|
// Carga los offsets desde el fichero
|
|
auto tf = loadFile(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
|
|
sprite_ = std::make_unique<Sprite>(texture, (SDL_FRect){0, 0, static_cast<float>(box_width_), static_cast<float>(box_height_)});
|
|
|
|
// Inicializa variables
|
|
fixed_width_ = false;
|
|
}
|
|
|
|
// Constructor
|
|
Text::Text(const std::shared_ptr<Texture> &texture, const std::shared_ptr<Text::File> &text_file) {
|
|
// 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
|
|
sprite_ = std::make_unique<Sprite>(texture, (SDL_FRect){0, 0, static_cast<float>(box_width_), static_cast<float>(box_height_)});
|
|
|
|
// Inicializa variables
|
|
fixed_width_ = false;
|
|
}
|
|
|
|
// Constructor con textura blanca opcional
|
|
Text::Text(const std::shared_ptr<Texture> &texture, const std::shared_ptr<Texture> &white_texture, const std::string &text_file) {
|
|
// Carga los offsets desde el fichero
|
|
auto tf = loadFile(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
|
|
sprite_ = std::make_unique<Sprite>(texture, (SDL_FRect){0, 0, static_cast<float>(box_width_), static_cast<float>(box_height_)});
|
|
white_sprite_ = std::make_unique<Sprite>(white_texture, (SDL_FRect){0, 0, static_cast<float>(box_width_), static_cast<float>(box_height_)});
|
|
|
|
// Inicializa variables
|
|
fixed_width_ = false;
|
|
}
|
|
|
|
// Constructor con textura blanca opcional
|
|
Text::Text(const std::shared_ptr<Texture> &texture, const std::shared_ptr<Texture> &white_texture, const std::shared_ptr<Text::File> &text_file) {
|
|
// 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
|
|
sprite_ = std::make_unique<Sprite>(texture, (SDL_FRect){0, 0, static_cast<float>(box_width_), static_cast<float>(box_height_)});
|
|
white_sprite_ = std::make_unique<Sprite>(white_texture, (SDL_FRect){0, 0, static_cast<float>(box_width_), static_cast<float>(box_height_)});
|
|
|
|
// Inicializa variables
|
|
fixed_width_ = false;
|
|
}
|
|
|
|
// Escribe texto en pantalla
|
|
void Text::write(int x, int y, const std::string &text, int kerning, int length) {
|
|
int shift = 0;
|
|
const std::string_view VISIBLE_TEXT = (length == -1) ? std::string_view(text) : std::string_view(text).substr(0, length);
|
|
|
|
sprite_->setY(y);
|
|
for (const auto CH : VISIBLE_TEXT) {
|
|
const auto INDEX = static_cast<unsigned char>(CH);
|
|
|
|
if (INDEX < offset_.size()) {
|
|
sprite_->setSpriteClip(offset_[INDEX].x, offset_[INDEX].y, box_width_, box_height_);
|
|
sprite_->setX(x + shift);
|
|
sprite_->render();
|
|
shift += offset_[INDEX].w + kerning;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Escribe el texto al doble de tamaño
|
|
void Text::write2X(int x, int y, const std::string &text, int kerning, int length) {
|
|
int shift = 0;
|
|
const std::string_view VISIBLE_TEXT = (length == -1) ? std::string_view(text) : std::string_view(text).substr(0, length);
|
|
|
|
for (const auto CH : VISIBLE_TEXT) {
|
|
const auto INDEX = static_cast<unsigned char>(CH);
|
|
|
|
if (INDEX < offset_.size()) {
|
|
SDL_FRect rect = {
|
|
static_cast<float>(offset_[INDEX].x),
|
|
static_cast<float>(offset_[INDEX].y),
|
|
static_cast<float>(box_width_),
|
|
static_cast<float>(box_height_)};
|
|
|
|
sprite_->getTexture()->render(x + shift, y, &rect, 2.0F, 2.0F);
|
|
shift += (offset_[INDEX].w + kerning) * 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Escribe el texto en una textura
|
|
auto Text::writeToTexture(const std::string &text, int zoom, int kerning, int length) -> std::shared_ptr<Texture> {
|
|
auto *renderer = Screen::get()->getRenderer();
|
|
auto texture = std::make_shared<Texture>(renderer);
|
|
auto width = Text::length(text, kerning) * zoom;
|
|
auto height = box_height_ * zoom;
|
|
auto *temp = SDL_GetRenderTarget(renderer);
|
|
|
|
texture->createBlank(width, height, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET);
|
|
texture->setBlendMode(SDL_BLENDMODE_BLEND);
|
|
texture->setAsRenderTarget(renderer);
|
|
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
|
|
SDL_RenderClear(renderer);
|
|
zoom == 1 ? write(0, 0, text, kerning) : write2X(0, 0, text, kerning);
|
|
SDL_SetRenderTarget(renderer, temp);
|
|
|
|
return texture;
|
|
}
|
|
|
|
// Escribe el texto con extras en una textura
|
|
auto Text::writeDXToTexture(Uint8 flags, const std::string &text, int kerning, Color text_color, Uint8 shadow_distance, Color shadow_color, int length) -> std::shared_ptr<Texture> {
|
|
auto *renderer = Screen::get()->getRenderer();
|
|
auto texture = std::make_shared<Texture>(renderer);
|
|
|
|
// Calcula las dimensiones considerando los efectos
|
|
auto base_width = Text::length(text, kerning);
|
|
auto base_height = box_height_;
|
|
auto width = base_width;
|
|
auto height = base_height;
|
|
auto offset_x = 0;
|
|
auto offset_y = 0;
|
|
|
|
const auto STROKED = ((flags & Text::STROKE) == Text::STROKE);
|
|
const auto SHADOWED = ((flags & Text::SHADOW) == Text::SHADOW);
|
|
|
|
if (STROKED) {
|
|
// Para stroke, el texto se expande en todas las direcciones por shadow_distance
|
|
width = base_width + (shadow_distance * 2);
|
|
height = base_height + (shadow_distance * 2);
|
|
offset_x = shadow_distance;
|
|
offset_y = shadow_distance;
|
|
} else if (SHADOWED) {
|
|
// Para shadow, solo se añade espacio a la derecha y abajo
|
|
width = base_width + shadow_distance;
|
|
height = base_height + shadow_distance;
|
|
}
|
|
|
|
auto *temp = SDL_GetRenderTarget(renderer);
|
|
|
|
texture->createBlank(width, height, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET);
|
|
texture->setBlendMode(SDL_BLENDMODE_BLEND);
|
|
texture->setAsRenderTarget(renderer);
|
|
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
|
|
SDL_RenderClear(renderer);
|
|
writeDX(flags, offset_x, offset_y, text, kerning, text_color, shadow_distance, shadow_color, length);
|
|
SDL_SetRenderTarget(renderer, temp);
|
|
|
|
return texture;
|
|
}
|
|
|
|
// Escribe el texto con colores
|
|
void Text::writeColored(int x, int y, const std::string &text, Color color, int kerning, int length) {
|
|
writeColoredWithSprite(sprite_.get(), x, y, text, color, kerning, length);
|
|
}
|
|
|
|
// Escribe el texto con colores usando un sprite específico
|
|
void Text::writeColoredWithSprite(Sprite* sprite, int x, int y, const std::string &text, Color color, int kerning, int length) {
|
|
int shift = 0;
|
|
const std::string_view VISIBLE_TEXT = (length == -1) ? std::string_view(text) : std::string_view(text).substr(0, length);
|
|
|
|
auto *texture = sprite->getTexture().get();
|
|
|
|
// Guarda el alpha original y aplica el nuevo
|
|
Uint8 original_alpha;
|
|
SDL_GetTextureAlphaMod(texture->getSDLTexture(), &original_alpha);
|
|
texture->setAlpha(color.a);
|
|
texture->setColor(color.r, color.g, color.b);
|
|
|
|
sprite->setY(y);
|
|
for (const auto CH : VISIBLE_TEXT) {
|
|
const auto INDEX = static_cast<unsigned char>(CH);
|
|
|
|
if (INDEX < offset_.size()) {
|
|
sprite->setSpriteClip(offset_[INDEX].x, offset_[INDEX].y, box_width_, box_height_);
|
|
sprite->setX(x + shift);
|
|
sprite->render();
|
|
shift += offset_[INDEX].w + kerning;
|
|
}
|
|
}
|
|
|
|
// Restaura los valores originales
|
|
texture->setColor(255, 255, 255);
|
|
texture->setAlpha(255);
|
|
}
|
|
|
|
// Escribe stroke con alpha correcto usando textura temporal
|
|
void Text::writeStrokeWithAlpha(int x, int y, const std::string &text, int kerning, Color stroke_color, Uint8 shadow_distance, int length) {
|
|
auto *renderer = Screen::get()->getRenderer();
|
|
auto *original_target = SDL_GetRenderTarget(renderer);
|
|
|
|
// Calcula dimensiones de la textura temporal
|
|
auto text_width = Text::length(text, kerning);
|
|
auto text_height = box_height_;
|
|
auto temp_width = text_width + (shadow_distance * 2);
|
|
auto temp_height = text_height + (shadow_distance * 2);
|
|
|
|
// Crea textura temporal
|
|
auto temp_texture = std::make_shared<Texture>(renderer);
|
|
temp_texture->createBlank(temp_width, temp_height, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET);
|
|
temp_texture->setBlendMode(SDL_BLENDMODE_BLEND);
|
|
|
|
// Renderiza el stroke en la textura temporal
|
|
temp_texture->setAsRenderTarget(renderer);
|
|
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
|
|
SDL_RenderClear(renderer);
|
|
|
|
// Selecciona el sprite apropiado para el stroke
|
|
auto *stroke_sprite = white_sprite_ ? white_sprite_.get() : sprite_.get();
|
|
|
|
// Renderiza stroke sin alpha (sólido) en textura temporal
|
|
Color solid_color = Color(stroke_color.r, stroke_color.g, stroke_color.b, 255);
|
|
for (int dist = 1; dist <= shadow_distance; ++dist) {
|
|
for (int dy = -dist; dy <= dist; ++dy) {
|
|
for (int dx = -dist; dx <= dist; ++dx) {
|
|
writeColoredWithSprite(stroke_sprite, shadow_distance + dx, shadow_distance + dy, text, solid_color, kerning, length);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Restaura render target original
|
|
SDL_SetRenderTarget(renderer, original_target);
|
|
|
|
// Renderiza la textura temporal con el alpha deseado
|
|
temp_texture->setAlpha(stroke_color.a);
|
|
temp_texture->render(x - shadow_distance, y - shadow_distance);
|
|
}
|
|
|
|
// Escribe el texto con sombra
|
|
void Text::writeShadowed(int x, int y, const std::string &text, Color color, Uint8 shadow_distance, int kerning, int length) {
|
|
writeDX(Text::SHADOW, x, y, text, kerning, color, shadow_distance, color, length);
|
|
write(x, y, text, kerning, length); // Dibuja el texto principal encima
|
|
}
|
|
|
|
// Escribe el texto centrado en un punto x
|
|
void Text::writeCentered(int x, int y, const std::string &text, int kerning, int length) {
|
|
x -= (Text::length(text, kerning) / 2);
|
|
write(x, y, text, kerning, length);
|
|
}
|
|
|
|
// 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 length) {
|
|
const auto CENTERED = ((flags & Text::CENTER) == Text::CENTER);
|
|
const auto SHADOWED = ((flags & Text::SHADOW) == Text::SHADOW);
|
|
const auto COLORED = ((flags & Text::COLOR) == Text::COLOR);
|
|
const auto STROKED = ((flags & Text::STROKE) == Text::STROKE);
|
|
|
|
if (CENTERED) {
|
|
x -= (Text::length(text, kerning) / 2);
|
|
}
|
|
|
|
if (SHADOWED) {
|
|
if (white_sprite_) {
|
|
writeColoredWithSprite(white_sprite_.get(), x + shadow_distance, y + shadow_distance, text, shadow_color, kerning, length);
|
|
} else {
|
|
writeColored(x + shadow_distance, y + shadow_distance, text, shadow_color, kerning, length);
|
|
}
|
|
}
|
|
|
|
if (STROKED) {
|
|
if (shadow_color.a < 255) {
|
|
// Usa textura temporal para alpha correcto
|
|
writeStrokeWithAlpha(x, y, text, kerning, shadow_color, shadow_distance, length);
|
|
} else {
|
|
// Método tradicional para stroke sólido
|
|
for (int dist = 1; dist <= shadow_distance; ++dist) {
|
|
for (int dy = -dist; dy <= dist; ++dy) {
|
|
for (int dx = -dist; dx <= dist; ++dx) {
|
|
if (white_sprite_) {
|
|
writeColoredWithSprite(white_sprite_.get(), x + dx, y + dy, text, shadow_color, kerning, length);
|
|
} else {
|
|
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);
|
|
}
|
|
}
|
|
|
|
// Escribe texto a partir de un TextStyle
|
|
void Text::writeStyle(int x, int y, const std::string &text, const Style &style, int length) {
|
|
writeDX(style.flags, x, y, text, style.kerning, style.text_color, style.shadow_distance, style.shadow_color);
|
|
}
|
|
|
|
// Obtiene la longitud en pixels de una cadena
|
|
auto Text::length(const std::string &text, int kerning) const -> int {
|
|
int shift = 0;
|
|
for (const auto &ch : text) {
|
|
// Convertimos a unsigned char para obtener el valor ASCII correcto (0-255)
|
|
const auto INDEX = static_cast<unsigned char>(ch);
|
|
|
|
// Verificamos si el carácter está dentro de los límites del array
|
|
if (INDEX < offset_.size()) {
|
|
shift += (offset_[INDEX].w + kerning);
|
|
}
|
|
}
|
|
|
|
// Descuenta el kerning del último caracter si el texto no está vacío
|
|
return text.empty() ? 0 : shift - kerning;
|
|
}
|
|
|
|
// Devuelve el valor de la variable
|
|
auto Text::getCharacterSize() const -> int {
|
|
return box_width_;
|
|
}
|
|
|
|
// Establece si se usa un tamaño fijo de letra
|
|
void Text::setFixedWidth(bool value) {
|
|
fixed_width_ = value;
|
|
}
|
|
|
|
// Llena una estructuta TextFile desde un fichero
|
|
auto Text::loadFile(const std::string &file_path) -> std::shared_ptr<Text::File> {
|
|
auto tf = std::make_shared<Text::File>();
|
|
|
|
// Inicializa a cero el vector con las coordenadas
|
|
for (auto &i : tf->offset) {
|
|
i.x = 0;
|
|
i.y = 0;
|
|
i.w = 0;
|
|
tf->box_width = 0;
|
|
tf->box_height = 0;
|
|
}
|
|
|
|
// Intenta cargar desde ResourceHelper primero
|
|
auto resource_data = ResourceHelper::loadFile(file_path);
|
|
std::istringstream stream;
|
|
bool using_resource_data = false;
|
|
|
|
if (!resource_data.empty()) {
|
|
std::string content(resource_data.begin(), resource_data.end());
|
|
stream.str(content);
|
|
using_resource_data = true;
|
|
}
|
|
|
|
// Fallback a archivo directo
|
|
std::ifstream file;
|
|
if (!using_resource_data) {
|
|
file.open(file_path);
|
|
}
|
|
|
|
std::istream& input_stream = using_resource_data ? stream : static_cast<std::istream&>(file);
|
|
|
|
if ((using_resource_data && stream.good()) || (!using_resource_data && file.is_open() && file.good())) {
|
|
std::string buffer;
|
|
|
|
// Lee los dos primeros valores del fichero
|
|
std::getline(input_stream, buffer);
|
|
std::getline(input_stream, buffer);
|
|
tf->box_width = std::stoi(buffer);
|
|
|
|
std::getline(input_stream, buffer);
|
|
std::getline(input_stream, buffer);
|
|
tf->box_height = std::stoi(buffer);
|
|
|
|
// lee el resto de datos del fichero
|
|
auto index = 32;
|
|
auto line_read = 0;
|
|
while (std::getline(input_stream, buffer)) {
|
|
// Almacena solo las lineas impares
|
|
if (line_read % 2 == 1) {
|
|
tf->offset[index++].w = std::stoi(buffer);
|
|
}
|
|
|
|
// Limpia el buffer
|
|
buffer.clear();
|
|
line_read++;
|
|
};
|
|
|
|
// Cierra el fichero si se usó
|
|
printWithDots("Text File : ", getFileName(file_path), "[ LOADED ]");
|
|
if (!using_resource_data && file.is_open()) {
|
|
file.close();
|
|
}
|
|
}
|
|
|
|
// El fichero no se puede abrir
|
|
else {
|
|
std::cerr << "Error: Fichero no encontrado " << getFileName(file_path) << '\n';
|
|
throw std::runtime_error("Fichero no encontrado: " + getFileName(file_path));
|
|
}
|
|
|
|
// Establece las coordenadas para cada caracter ascii de la cadena y su ancho
|
|
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;
|
|
}
|
|
|
|
return tf;
|
|
} |