You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

154 lines
3.8 KiB

#include "DiabloUI/ttf_render_wrapped.h"
#include <cstddef>
#include <cstring>
#include <vector>
#include <algorithm>
#include <SDL.h>
#ifdef USE_SDL1
#include "utils/sdl2_to_1_2_backports.h"
#else
#include "utils/sdl2_backports.h"
#endif
#include "utils/log.hpp"
#include "utils/sdl_compat.h"
#include "utils/sdl_wrap.h"
#include "utils/ttf_wrap.h"
namespace devilution {
namespace {
bool CharacterIsDelimiter(char c)
{
constexpr char Delimiters[] = { ' ', '\t', '\r', '\n' };
return std::find(std::begin(Delimiters), std::end(Delimiters), c) != std::end(Delimiters);
}
} // namespace
// Based on SDL 2.0.12 TTF_RenderUTF8_Blended_Wrapped
SDLSurfaceUniquePtr RenderUTF8_Solid_Wrapped(TTF_Font *font, const char *text, SDL_Color fg, Uint32 wrapLength, const int xAlign)
{
int width = 0;
int height = 0;
const int lineSpace = 2;
/* Get the dimensions of the text surface */
if (TTF_SizeUTF8(font, text, &width, &height) < 0 || width == 0) {
TTF_SetError("Text has zero width");
return {};
}
std::unique_ptr<char[]> str;
std::vector<char *> strLines;
if (wrapLength > 0 && *text != '\0') {
const std::size_t strLen = std::strlen(text);
str.reset(new char[strLen + 1]);
std::memcpy(str.get(), text, strLen + 1);
char *tok = str.get();
char *end = str.get() + strLen;
do {
strLines.push_back(tok);
/* Look for the end of the line */
char *spot;
if ((spot = SDL_strchr(tok, '\r')) != nullptr || (spot = SDL_strchr(tok, '\n')) != nullptr) {
if (*spot == '\r') {
++spot;
}
if (*spot == '\n') {
++spot;
}
} else {
spot = end;
}
char *nextTok = spot;
/* Get the longest string that will fit in the desired space */
for (;;) {
/* Strip trailing whitespace */
while (spot > tok && CharacterIsDelimiter(spot[-1])) {
--spot;
}
if (spot == tok) {
if (CharacterIsDelimiter(*spot)) {
*spot = '\0';
}
break;
}
char delim = *spot;
*spot = '\0';
int w = 0;
int h = 0;
TTF_SizeUTF8(font, tok, &w, &h);
if ((Uint32)w <= wrapLength) {
break;
}
/* Back up and try again... */
*spot = delim;
while (spot > tok && !CharacterIsDelimiter(spot[-1])) {
--spot;
}
if (spot > tok) {
nextTok = spot;
}
}
tok = nextTok;
} while (tok < end);
}
if (strLines.empty())
return TTFWrap::RenderUTF8_Solid(font, text, fg);
/* Create the target surface */
auto textbuf = SDLWrap::CreateRGBSurface(SDL_SWSURFACE, (strLines.size() > 1) ? wrapLength : width, height * strLines.size() + (lineSpace * (strLines.size() - 1)), 8, 0, 0, 0, 0);
/* Fill the palette with the foreground color */
SDL_Palette *palette = textbuf->format->palette;
palette->colors[0].r = 255 - fg.r;
palette->colors[0].g = 255 - fg.g;
palette->colors[0].b = 255 - fg.b;
palette->colors[1].r = fg.r;
palette->colors[1].g = fg.g;
palette->colors[1].b = fg.b;
SDLC_SetColorKey(textbuf.get(), 0);
// Reduced space between lines to roughly match Diablo.
const int lineskip = TTF_FontLineSkip(font) * 7 / 10; // avoids forced int > float > int conversion
SDL_Rect dest = { 0, 0, 0, 0 };
for (auto text : strLines) {
if (*text == '\0') {
dest.y += lineskip;
continue;
}
SDLSurfaceUniquePtr tmp = TTFWrap::RenderUTF8_Solid(font, text, fg);
dest.w = static_cast<Uint16>(tmp->w);
dest.h = static_cast<Uint16>(tmp->h);
switch (xAlign) {
case TextAlignment_END:
dest.x = textbuf->w - tmp->w;
break;
case TextAlignment_CENTER:
dest.x = (textbuf->w - tmp->w) / 2;
break;
case TextAlignment_BEGIN:
dest.x = 0;
break;
}
SDL_BlitSurface(tmp.get(), nullptr, textbuf.get(), &dest);
dest.y += lineskip;
}
return textbuf;
}
} // namespace devilution