spacebox/src/Text.cpp
Cocktail Frank c1f8edace7 Add functions for reading sprite and text attributes
Add a function for reading a sprite's translation. Add functions for
reading a text object's foreground, background, and font.

Log a warning when the draw function of a sprite containing textures is
called but the currently active texture will not be drawn because it
has not been generated yet.

Line-length linting.
2025-02-18 16:23:59 -05:00

226 lines
6.1 KiB
C++

/* +=======================================================+
____/ \____ /: Open source game framework licensed to freely use, :
\ / / : copy, and modify - created for dank.game :
+==\ ^__^ /==+ : :
: ~/ \~ : : Download at https://open.shampoo.ooo/shampoo/spacebox :
: ~~~~~~~~~~~~ : +=======================================================+
: SPACE ~~~~~ : /
: ~~~~~~~ BOX :/
+=============*/
#include "Text.hpp"
using namespace sb;
void Text::bind_textures() const
{
try
{
Model::bind_textures();
}
catch (const std::runtime_error& error)
{
std::ostringstream message;
message << "Error binding textures for " << *this << ": " << error.what();
throw std::runtime_error(message.str());
}
}
void Text::content(const std::string& content)
{
_content = content;
}
void Text::content(char character)
{
_content = "";
_content += character;
}
const std::string& Text::content() const
{
return _content;
}
void Text::foreground(const Color& foreground)
{
_foreground = foreground;
}
const sb::Color& Text::foreground() const
{
return _foreground;
}
void Text::background(const Color& background)
{
_background = background;
}
const sb::Color& Text::background() const
{
return _background;
}
void Text::font(std::shared_ptr<TTF_Font> font)
{
_font = font;
}
const std::shared_ptr<TTF_Font> Text::font() const
{
return _font;
}
void Text::dimensions(const glm::ivec2& dimensions)
{
_dimensions = dimensions;
}
glm::ivec2 Text::dimensions() const
{
if (_dimensions.has_value())
{
return *_dimensions;
}
else
{
int width;
int height;
if (TTF_SizeUTF8(_font.get(), _content.c_str(), &width, &height) == 0)
{
return {width, height};
}
else
{
std::ostringstream message;
message << "Error checking dimensions of text content \"" << _content << "\":";
sb::Log::sdl_error(message.str());
return {0, 0};
}
}
}
void Text::scaling_quality(GLint quality)
{
_scaling_quality = quality;
}
void Text::wrap(int width)
{
_wrap = width;
}
void Text::offset(const glm::vec2& amount)
{
_offset = amount;
}
void Text::refresh()
{
/* Render the text with transparent background as RGBA pixel data using the SDL image library. Apply wrap if it is set. */
std::shared_ptr<SDL_Surface> blended;
if (_wrap.has_value())
{
blended = std::shared_ptr<SDL_Surface>{
TTF_RenderUTF8_Blended_Wrapped(
_font.get(), _content.c_str(), _foreground, _wrap.value()), SDL_FreeSurface};
}
else
{
blended = std::shared_ptr<SDL_Surface>{
TTF_RenderUTF8_Blended(_font.get(), _content.c_str(), _foreground), SDL_FreeSurface};
}
/* Composite the rendered text onto a background surface. */
if (!blended)
{
Log::sdl_error("Could not create text");
}
else
{
/* Set the text surface blend mode to no blending if the background if fully transparent, or to blend if there
* is a visible background surface. If it fails, print an error and continue anyway.
*
* A better algorithm may be to do BLEND_MAX, but that will need to be custom built using
* SDL_ComposeCustomBlendMode.
*/
SDL_BlendMode blend;
if (_background.normal().a <= 0.0f)
{
blend = SDL_BLENDMODE_NONE;
}
else
{
blend = SDL_BLENDMODE_BLEND;
}
if (SDL_SetSurfaceBlendMode(blended.get(), blend) != 0)
{
Log::sdl_error("Could not set blend mode on text surface");
}
/* Use the size of the rendered text unless dimensions has been set. */
glm::ivec2 dimensions;
if (_dimensions.has_value())
{
dimensions = _dimensions.value();
}
else
{
dimensions = glm::ivec2 {blended->w, blended->h};
}
/* Create a container surface with the same format as the rendered text that the rendered text will be composited onto. */
std::shared_ptr<SDL_Surface> container
{
SDL_CreateRGBSurfaceWithFormat(0, dimensions.x, dimensions.y, blended->format->BitsPerPixel, blended->format->format),
SDL_FreeSurface
};
if (!container)
{
Log::sdl_error("Could not create container surface for rendered text");
}
else
{
SDL_FillRect(container.get(), nullptr, _background);
Box blended_box {0.0f, 0.0f, float(blended->w), float(blended->h), false};
Box container_box {0.0f, 0.0f, float(container->w), float(container->h), false};
blended_box.center(container_box.center());
blended_box.move(_offset);
SDL_Rect r = blended_box;
SDL_BlitSurface(blended.get(), nullptr, container.get(), &r);
blended = container;
}
/* Rotate and mirror the surface for compatibility with OpenGL */
std::shared_ptr<SDL_Surface> flipped {rotozoomSurfaceXY(blended.get(), 0, 1, -1, 0), SDL_FreeSurface};
if (!flipped)
{
Log::sdl_error("Could not flip surface");
}
else
{
/* Generate texture and create storage. Load pixels from the text rendering surface into the texture. The texture object
* will handle destroying the previous texture. The texture object is optimized so that it won't reallocate pixel memory
* if it was previously generated at the same size. */
texture(0).generate({flipped->w, flipped->h}, GL_RGBA8, _scaling_quality);
texture(0).load(flipped.get());
}
}
}
Text::operator std::string() const
{
std::ostringstream message;
message << "<sb::Text \"" << _content << "\">";
return message.str();
}
std::ostream& sb::operator<<(std::ostream& out, const Text& text)
{
out << std::string(text);
return out;
}