C, not C++. Just want to say that upfront.
So, we maintain an old libre open source game called Project: Starfighter, originally developed by Parallel Realities in 2003. As you might imagine, it didn't originally support Unicode; we added unicode support (or more specifically UTF-8 support) in later.
Unfortunately it seems one aspect of the Unicode system we implemented is broken: line wrapping. We thought we had this solved with the Pango library (the only one we could find), which reports where line breaks can be made… but it reports on a grapheme cluster level, not at a code point level. So if a grapheme cluster (like, say, あ) contains multiple code points (three in this case), it reports all three as breakable because it's actually reporting for the character and not the specific code point.
Full detail here: https://github.com/pr-starfighter/starfighter/issues/9
So my question is, can anyone offer insight on what should be done here? For reference, this is the function that's supposed to handle this:
int gfx_renderUnicodeBase(const char *in, int x, int y, int real_x, int fontColor, int wrap, SDL_Surface *dest)
{
SDL_Surface *textSurf;
SDL_Color color;
int w, h;
int avail_w;
int changed;
int breakPoints[STRMAX];
int nBreakPoints;
char testStr[STRMAX];
char remainingStr[STRMAX];
PangoLogAttr logAttrs[STRMAX];
int nLogAttrs;
int i;
SDL_Rect area;
if (strcmp(in, "") == 0)
return y;
avail_w = dest->w - real_x;
switch (fontColor)
{
case FONT_WHITE:
color.r = 255;
color.g = 255;
color.b = 255;
break;
case FONT_RED:
color.r = 255;
color.g = 0;
color.b = 0;
break;
case FONT_YELLOW:
color.r = 255;
color.g = 255;
color.b = 0;
break;
case FONT_GREEN:
color.r = 0;
color.g = 255;
color.b = 0;
break;
case FONT_CYAN:
color.r = 0;
color.g = 255;
color.b = 255;
break;
case FONT_OUTLINE:
color.r = 0;
color.g = 0;
color.b = 10;
break;
default:
color.r = 255;
color.g = 255;
color.b = 255;
}
if (gfx_unicodeFont != NULL)
{
strcpy(remainingStr, in);
if (TTF_SizeUTF8(gfx_unicodeFont, remainingStr, &amp;w, &amp;h) < 0)
{
engine_error(TTF_GetError());
}
changed = wrap;
while (changed &amp;&amp; (w > avail_w))
{
nLogAttrs = strlen(remainingStr) + 1;
pango_get_log_attrs(remainingStr, strlen(remainingStr), -1, NULL, logAttrs, nLogAttrs);
nBreakPoints = 0;
for (i = 0; i < nLogAttrs; i++)
{
if (logAttrs[i].is_line_break)
{
breakPoints[nBreakPoints] = i;
nBreakPoints++;
}
}
changed = 0;
for (i = nBreakPoints - 1; i >= 0; i--)
{
strncpy(testStr, remainingStr, breakPoints[i]);
testStr[breakPoints[i]] = '\0';
if (TTF_SizeUTF8(gfx_unicodeFont, testStr, &amp;w, &amp;h) < 0)
{
engine_error(TTF_GetError());
}
if (w <= avail_w)
{
textSurf = TTF_RenderUTF8_Blended(gfx_unicodeFont, testStr, color);
if (textSurf == NULL)
{
printf("While rendering testStr \"%s\" as unicode...\n", testStr);
engine_error("Attempted to render UTF8, got null surface!");
}
area.x = x;
area.y = y;
area.w = textSurf->w;
area.h = textSurf->h;
if (SDL_BlitSurface(textSurf, NULL, dest, &amp;area) < 0)
{
printf("BlitSurface error: %s\n", SDL_GetError());
engine_showError(2, "");
}
SDL_FreeSurface(textSurf);
textSurf = NULL;
y += TTF_FontHeight(gfx_unicodeFont) + 1;
memmove(remainingStr, remainingStr + breakPoints[i],
(strlen(remainingStr) - breakPoints[i]) + 1);
changed = 1;
break;
}
}
if (TTF_SizeUTF8(gfx_unicodeFont, remainingStr, &amp;w, &amp;h) < 0)
{
engine_error(TTF_GetError());
}
}
textSurf = TTF_RenderUTF8_Blended(gfx_unicodeFont, remainingStr, color);
if (textSurf == NULL)
{
printf("While rendering remainingStr \"%s\" as unicode...\n", remainingStr);
engine_error("Attempted to render UTF8, got null surface!");
}
area.x = x;
area.y = y;
area.w = textSurf->w;
area.h = textSurf->h;
if (SDL_BlitSurface(textSurf, NULL, dest, &amp;area) < 0)
{
printf("BlitSurface error: %s\n", SDL_GetError());
engine_showError(2, "");
}
SDL_FreeSurface(textSurf);
textSurf = NULL;
y += TTF_FontHeight(gfx_unicodeFont) + 1;
}
else
{
engine_warn("gfx_unicodeFont is NULL!");
}
return y;
}