Browse Source

Wrapped text rendering for TTF

Does not do centering yet, but should be easy to add in the future
pull/314/head
Gleb Mazovetskiy 7 years ago committed by Anders Jenbo
parent
commit
5d7f6db2fc
  1. 1
      CMakeLists.txt
  2. 6
      SourceX/DiabloUI/dialogs.cpp
  3. 5
      SourceX/DiabloUI/text_draw.cpp
  4. 154
      SourceX/DiabloUI/ttf_render_wrapped.cpp
  5. 14
      SourceX/DiabloUI/ttf_render_wrapped.h

1
CMakeLists.txt

@ -215,6 +215,7 @@ set(devilutionx_SRCS
SourceX/DiabloUI/text_draw.cpp
SourceX/DiabloUI/text.cpp
SourceX/DiabloUI/title.cpp
SourceX/DiabloUI/ttf_render_wrapped.cpp
SourceX/main.cpp
./Packaging/macOS/AppIcon.icns
./Packaging/resources/CharisSILB.ttf)

6
SourceX/DiabloUI/dialogs.cpp

@ -44,7 +44,7 @@ constexpr auto DIALOG_ART_L = UiImage(&dialogArt, { 127, 100, 385, 280 });
UiItem OKCANCEL_DIALOG[] = {
DIALOG_ART_S,
UiText(dialogText, { 198, 211, 240, 80 }, UIS_CENTER),
UiText(dialogText, { 200, 211, 240, 80 }, UIS_CENTER),
MakeSmlButton("OK", &DialogActionOK, 200, 265),
MakeSmlButton("Cancel", &DialogActionCancel, 330, 265),
};
@ -57,8 +57,8 @@ UiItem OK_DIALOG[] = {
UiItem OK_DIALOG_WITH_CAPTION[] = {
DIALOG_ART_L,
UiText(dialogText, SDL_Color { 255, 255, 0, 0 }, { 200, 110, 240, 20 }, UIS_CENTER),
UiText(dialogCaption, { 200, 141, 240, 80 }, UIS_CENTER),
UiText(dialogText, SDL_Color { 255, 255, 0, 0 }, { 147, 110, 345, 20 }, UIS_CENTER),
UiText(dialogCaption, { 147, 141, 345, 190 }, UIS_CENTER),
MakeSmlButton("OK", &DialogActionOK, 264, 335),
};

5
SourceX/DiabloUI/text_draw.cpp

@ -4,6 +4,7 @@
#include "DiabloUI/fonts.h"
#include "DiabloUI/text.h"
#include "DiabloUI/ui_item.h"
#include "DiabloUI/ttf_render_wrapped.h"
namespace dvl {
@ -28,8 +29,8 @@ void DrawTTF(const char *text, const SDL_Rect &rect, int flags,
{
if (*render_cache == nullptr) {
*render_cache = new TtfSurfaceCache();
(*render_cache)->text = TTF_RenderUTF8_Solid(font, text, text_color);
(*render_cache)->shadow = TTF_RenderUTF8_Solid(font, text, shadow_color);
(*render_cache)->text = RenderUTF8_Solid_Wrapped(font, text, text_color, rect.w);
(*render_cache)->shadow = RenderUTF8_Solid_Wrapped(font, text, shadow_color, rect.w);
}
SDL_Surface *text_surface = (*render_cache)->text;
SDL_Surface *shadow_surface = (*render_cache)->shadow;

154
SourceX/DiabloUI/ttf_render_wrapped.cpp

@ -0,0 +1,154 @@
#include "DiabloUI/ttf_render_wrapped.h"
#include <cstddef>
namespace dvl {
namespace {
SDL_bool CharacterIsDelimiter(char c, const char *delimiters)
{
while (*delimiters) {
if (c == *delimiters)
return SDL_TRUE;
++delimiters;
}
return SDL_FALSE;
}
} // namespace
// Based on SDL 2.0.12 TTF_RenderUTF8_Blended_Wrapped
SDL_Surface *RenderUTF8_Solid_Wrapped(TTF_Font *font, const char *text, SDL_Color fg, Uint32 wrapLength)
{
int width, height;
SDL_Surface *textbuf;
const int lineSpace = 2;
char *str, **strLines;
/* Get the dimensions of the text surface */
if ((TTF_SizeUTF8(font, text, &width, &height) < 0) || !width) {
TTF_SetError("Text has zero width");
return nullptr;
}
std::size_t numLines = 1;
str = nullptr;
strLines = nullptr;
if (wrapLength > 0 && *text) {
const char *wrapDelims = " \t\r\n";
int w, h;
char *spot, *tok, *next_tok, *end;
char delim;
size_t str_len = SDL_strlen(text);
numLines = 0;
str = SDL_stack_alloc(char, str_len + 1);
if (str == nullptr) {
TTF_SetError("Out of memory");
return nullptr;
}
SDL_strlcpy(str, text, str_len + 1);
tok = str;
end = str + str_len;
do {
strLines = (char **)SDL_realloc(strLines, (numLines + 1) * sizeof(*strLines));
if (!strLines) {
TTF_SetError("Out of memory");
return nullptr;
}
strLines[numLines++] = tok;
/* Look for the end of the line */
if ((spot = SDL_strchr(tok, '\r')) != nullptr || (spot = SDL_strchr(tok, '\n')) != nullptr) {
if (*spot == '\r') {
++spot;
}
if (*spot == '\n') {
++spot;
}
} else {
spot = end;
}
next_tok = spot;
/* Get the longest string that will fit in the desired space */
for (;;) {
/* Strip trailing whitespace */
while (spot > tok && CharacterIsDelimiter(spot[-1], wrapDelims)) {
--spot;
}
if (spot == tok) {
if (CharacterIsDelimiter(*spot, wrapDelims)) {
*spot = '\0';
}
break;
}
delim = *spot;
*spot = '\0';
TTF_SizeUTF8(font, tok, &w, &h);
if ((Uint32)w <= wrapLength) {
break;
} else {
/* Back up and try again... */
*spot = delim;
}
while (spot > tok && !CharacterIsDelimiter(spot[-1], wrapDelims)) {
--spot;
}
if (spot > tok) {
next_tok = spot;
}
}
tok = next_tok;
} while (tok < end);
}
if (!strLines) {
return TTF_RenderUTF8_Solid(font, text, fg);
}
/* Create the target surface */
textbuf = SDL_CreateRGBSurface(SDL_SWSURFACE, (numLines > 1) ? wrapLength : width, height * numLines + (lineSpace * (numLines - 1)), 8, 0, 0, 0, 0);
if (textbuf == nullptr) {
if (strLines) {
SDL_free(strLines);
SDL_stack_free(str);
}
return nullptr;
}
/* 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;
#ifdef USE_SDL1
SDL_SetColorKey(textbuf, SDL_SRCCOLORKEY, 0);
#else
SDL_SetColorKey(textbuf, SDL_TRUE, 0);
#endif
SDL_Rect dest = { 0, 0, 0, 0 };
for (std::size_t line = 0; line < numLines; line++) {
text = strLines[line];
SDL_Surface *tmp = TTF_RenderUTF8_Solid(font, text, fg);
dest.w = static_cast<decltype(SDL_Rect().w)>(tmp->w);
dest.h = static_cast<decltype(SDL_Rect().h)>(tmp->h);
SDL_BlitSurface(tmp, nullptr, textbuf, &dest);
dest.y += tmp->h;
SDL_FreeSurface(tmp);
}
SDL_free(strLines);
SDL_stack_free(str);
return textbuf;
}
} // namespace dvl

14
SourceX/DiabloUI/ttf_render_wrapped.h

@ -0,0 +1,14 @@
#pragma once
#include "devilution.h"
#include <SDL_ttf.h>
namespace dvl {
// `TTF_RenderUTF8_Solid_Wrapped` is only available in SDL2 2.0.16 (unreleased
// as of this writing). This is a hacky alternative.
SDL_Surface *RenderUTF8_Solid_Wrapped(
TTF_Font *font, const char *text, SDL_Color fg, Uint32 wrapLength);
} // namespace dvl
Loading…
Cancel
Save