Fix text after an unclosed ^ tag not wrapping

This commit is contained in:
Kae 2024-04-23 13:27:57 +10:00
parent c24fc5aeaf
commit dd67777238
5 changed files with 87 additions and 82 deletions

View File

@ -1,7 +1,5 @@
{ {
"movementThreshold" : 0.5, "movementThreshold" : 0.5,
"bubbleOffset" : [0, 1.875], "bubbleOffset" : [0, 1.875],
"textStyle" : { "textStyle" : { "backDirectives" : "?border=1;111;1110?border=1;111;1117" }
"backDirectives" : "?border=1;000a;0000?border=1;000a;0004"
}
} }

View File

@ -31,21 +31,20 @@ TextStyle& TextStyle::loadJson(Json const& config) {
} }
namespace Text { namespace Text {
std::string const AllEsc = strf("{:c}{:c}", CmdEsc, StartEsc);
std::string const AllEscEnd = strf("{:c}{:c}{:c}", CmdEsc, StartEsc, EndEsc);
static auto stripEscapeRegex = std::regex(strf("\\{:c}[^;]*{:c}", CmdEsc, EndEsc)); static auto stripEscapeRegex = std::regex(strf("\\{:c}[^;]*{:c}", CmdEsc, EndEsc));
String stripEscapeCodes(String const& s) { String stripEscapeCodes(String const& s) {
return std::regex_replace(s.utf8(), stripEscapeRegex, ""); return std::regex_replace(s.utf8(), stripEscapeRegex, "");
} }
static std::string escapeChars = strf("{:c}{:c}", CmdEsc, StartEsc);
bool processText(StringView text, TextCallback textFunc, CommandsCallback commandsFunc, bool includeCommandSides) { bool processText(StringView text, TextCallback textFunc, CommandsCallback commandsFunc, bool includeCommandSides) {
std::string_view escChars(escapeChars);
std::string_view str = text.utf8(); std::string_view str = text.utf8();
while (true) { while (true) {
size_t escape = str.find_first_of(escChars); size_t escape = str.find_first_of(AllEsc);
if (escape != NPos) { if (escape != NPos) {
escape = str.find_first_not_of(escChars, escape) - 1; // jump to the last ^ escape = str.find_first_not_of(AllEsc, escape) - 1; // jump to the last ^
size_t end = str.find_first_of(EndEsc, escape); size_t end = str.find_first_of(EndEsc, escape);
if (end != NPos) { if (end != NPos) {

View File

@ -30,6 +30,8 @@ namespace Text {
unsigned char const EndEsc = ';'; unsigned char const EndEsc = ';';
unsigned char const CmdEsc = '^'; unsigned char const CmdEsc = '^';
unsigned char const SpecialCharLimit = ' '; unsigned char const SpecialCharLimit = ' ';
extern std::string const AllEsc;
extern std::string const AllEscEnd;
String stripEscapeCodes(String const& s); String stripEscapeCodes(String const& s);
inline bool isEscapeCode(Utf32Type c) { return c == CmdEsc || c == StartEsc; } inline bool isEscapeCode(Utf32Type c) { return c == CmdEsc || c == StartEsc; }

View File

@ -411,7 +411,7 @@ void Object::render(RenderCallback* renderCallback) {
renderCallback->addAudios(m_networkedAnimatorDynamicTarget.pullNewAudios()); renderCallback->addAudios(m_networkedAnimatorDynamicTarget.pullNewAudios());
renderCallback->addParticles(m_networkedAnimatorDynamicTarget.pullNewParticles()); renderCallback->addParticles(m_networkedAnimatorDynamicTarget.pullNewParticles());
if (m_networkedAnimator->parts().count() > 0) { if (m_networkedAnimator->constParts().size() > 0) {
renderCallback->addDrawables(m_networkedAnimator->drawables(position() + m_animationPosition + damageShake()), renderLayer()); renderCallback->addDrawables(m_networkedAnimator->drawables(position() + m_animationPosition + damageShake()), renderLayer());
} else { } else {
if (m_orientationIndex != NPos) if (m_orientationIndex != NPos)

View File

@ -130,34 +130,39 @@ int TextPainter::stringWidth(StringView s, unsigned charLimit) {
bool TextPainter::processWrapText(StringView text, unsigned* wrapWidth, WrapTextCallback textFunc) { bool TextPainter::processWrapText(StringView text, unsigned* wrapWidth, WrapTextCallback textFunc) {
String font = m_renderSettings.font, setFont = font; String font = m_renderSettings.font, setFont = font;
m_fontTextureGroup.switchFont(font); m_fontTextureGroup.switchFont(font);
auto iterator = text.begin(), end = text.end();
unsigned lines = 0; unsigned lines = 0;
auto lineStartIterator = iterator, splitIterator = end;
auto it = text.begin(); unsigned linePixelWidth = 0, splitPixelWidth = 0;
auto end = text.end(); size_t commandStart = NPos, commandEnd = NPos;
bool finished = true;
unsigned linePixelWidth = 0; // How wide is this line so far
unsigned lineCharSize = 0; // how many characters in this line ?
auto escIt = end;
unsigned splitWidth = 0; // How wide was the string there ?
auto lineStartIt = it; // Where does this line start ?
auto splitIt = end; // != end if we last saw a place to split the string
auto slice = [](StringView::const_iterator a, StringView::const_iterator b) -> StringView { auto slice = [](StringView::const_iterator a, StringView::const_iterator b) -> StringView {
const char* aPtr = &*a.base(); const char* aPtr = &*a.base();
return StringView(aPtr, &*b.base() - aPtr); return StringView(aPtr, &*b.base() - aPtr);
}; };
while (it != end) { while (iterator != end) {
auto character = *it; auto character = *iterator;
finished = false; // assume at least one character if we get here
if (Text::isEscapeCode(character)) bool noMoreCommands = commandStart != NPos && commandEnd == NPos;
escIt = it; if (!noMoreCommands && Text::isEscapeCode(character)) {
size_t index = &*iterator.base() - text.utf8Ptr();
if (escIt != end) { if (commandStart == NPos) {
if (character == Text::EndEsc) { for (size_t escOrEnd = commandStart = index;
StringView inner = slice(++escIt, it); (escOrEnd = text.utf8().find_first_of(Text::AllEscEnd, escOrEnd + 1)) != NPos;) {
if (text.utf8().at(escOrEnd) != Text::EndEsc)
commandStart = escOrEnd;
else {
commandEnd = escOrEnd;
break;
}
}
}
if (commandStart == index && commandEnd != NPos) {
const char* commandStr = text.utf8Ptr() + ++commandStart;
StringView inner(commandStr, commandEnd - commandStart);
inner.forEachSplitView(",", [&](StringView command, size_t, size_t) { inner.forEachSplitView(",", [&](StringView command, size_t, size_t) {
if (command == "reset") { if (command == "reset") {
m_fontTextureGroup.switchFont(font = setFont); m_fontTextureGroup.switchFont(font = setFont);
@ -167,63 +172,61 @@ bool TextPainter::processWrapText(StringView text, unsigned* wrapWidth, WrapText
m_fontTextureGroup.switchFont(font = command.substr(5)); m_fontTextureGroup.switchFont(font = command.substr(5));
} }
}); });
escIt = end; // jump the iterator to the character after the command
iterator = text.utf8().begin() + commandEnd + 1;
commandStart = commandEnd = NPos;
continue;
} }
lineCharSize++; }
// is this a linefeed / cr / whatever that forces a line split ?
if (character == '\n' || character == '\v') {
// knock one off the end because we don't render the CR
if (!textFunc(slice(lineStartIterator, iterator), lines++))
return false;
lineStartIterator = iterator;
++lineStartIterator;
// next line starts after the CR with no characters in it and no known splits.
linePixelWidth = 0;
splitIterator = end;
finished = true;
} else { } else {
lineCharSize++; // assume at least one character if we get here. int characterWidth = glyphWidth(character);
// is it a place where we might want to split the line ?
if (character == ' ' || character == '\t') {
splitIterator = iterator;
splitPixelWidth = linePixelWidth + characterWidth;
}
// is this a linefeed / cr / whatever that forces a line split ? // would the line be too long if we render this next character ?
if (character == '\n' || character == '\v') { if (wrapWidth && (linePixelWidth + characterWidth) > *wrapWidth) {
// knock one off the end because we don't render the CR // did we find somewhere to split the line ?
if (!textFunc(slice(lineStartIt, it), lines++)) if (splitIterator != end) {
return false; if (!textFunc(slice(lineStartIterator, splitIterator), lines++))
return false;
lineStartIt = it; // do not include the split character on the next line
++lineStartIt; unsigned stringWidth = linePixelWidth - splitPixelWidth;
// next line starts after the CR with no characters in it and no known splits. linePixelWidth = stringWidth + characterWidth;
lineCharSize = linePixelWidth = 0; lineStartIterator = ++splitIterator;
splitIt = end; splitIterator = end;
} else {
int charWidth = glyphWidth(character);
// is it a place where we might want to split the line ?
if (character == ' ' || character == '\t') {
splitIt = it;
splitWidth = linePixelWidth + charWidth; // the width of the string at
// the split point, i.e. after the space.
}
// would the line be too long if we render this next character ?
if (wrapWidth && (linePixelWidth + charWidth) > *wrapWidth) {
// did we find somewhere to split the line ?
if (splitIt != end) {
if (!textFunc(slice(lineStartIt, splitIt), lines++))
return false;
unsigned stringWidth = linePixelWidth - splitWidth;
linePixelWidth = stringWidth + charWidth; // and is as wide as the bit after the space.
lineStartIt = ++splitIt;
splitIt = end;
} else {
if (!textFunc(slice(lineStartIt, it), lines++))
return false;
lineStartIt = it; // include that character on the next line.
lineCharSize = 1; // next line has that character in
linePixelWidth = charWidth; // and is as wide as that character
}
} else { } else {
linePixelWidth += charWidth; if (!textFunc(slice(lineStartIterator, iterator), lines++))
return false;
// include that character on the next line
lineStartIterator = iterator;
linePixelWidth = characterWidth;
finished = false;
} }
} else {
linePixelWidth += characterWidth;
} }
} }
++it; ++iterator;
}; };
// if we hit the end of the string before hitting the end of the line. // if we hit the end of the string before hitting the end of the line
if (lineCharSize > 0) return finished || textFunc(slice(lineStartIterator, end), lines);
return textFunc(slice(lineStartIt, end), lines);
return true;
} }
List<StringView> TextPainter::wrapTextViews(StringView s, Maybe<unsigned> wrapWidth) { List<StringView> TextPainter::wrapTextViews(StringView s, Maybe<unsigned> wrapWidth) {
@ -293,18 +296,20 @@ void TextPainter::setProcessingDirectives(StringView directives, bool back) {
} }
void TextPainter::setFont(String const& font) { void TextPainter::setFont(String const& font) {
m_renderSettings.font = font; m_fontTextureGroup.switchFont(m_renderSettings.font = font);
} }
TextStyle& TextPainter::setTextStyle(TextStyle const& textStyle) { TextStyle& TextPainter::setTextStyle(TextStyle const& textStyle) {
TextStyle& style = m_renderSettings = textStyle; TextStyle& style = m_renderSettings = textStyle;
modifyDirectives(style.directives); modifyDirectives(style.directives);
modifyDirectives(style.backDirectives); modifyDirectives(style.backDirectives);
m_fontTextureGroup.switchFont(style.font);
return style; return style;
} }
void TextPainter::clearTextStyle() { void TextPainter::clearTextStyle() {
m_renderSettings = m_defaultRenderSettings; m_renderSettings = m_defaultRenderSettings;
m_fontTextureGroup.switchFont(m_renderSettings.font);
} }
void TextPainter::addFont(FontPtr const& font, String const& name) { void TextPainter::addFont(FontPtr const& font, String const& name) {
@ -343,17 +348,18 @@ void TextPainter::applyCommands(StringView unsplitCommands) {
try { try {
if (command == "reset") { if (command == "reset") {
m_renderSettings = m_savedRenderSettings; m_renderSettings = m_savedRenderSettings;
m_fontTextureGroup.switchFont(m_renderSettings.font);
} else if (command == "set") { } else if (command == "set") {
m_savedRenderSettings = m_renderSettings; m_savedRenderSettings = m_renderSettings;
} else if (command.beginsWith("shadow")) { } else if (command.beginsWith("shadow")) {
if (command.utf8Size() == 6) if (command.utf8Size() == 6)
m_renderSettings.shadow = Color::Black.toRgba(); m_renderSettings.shadow = Color::Black.toRgba();
else if (command[6] == '=') else if (command.utf8()[6] == '=')
m_renderSettings.shadow = Color(command.substr(7)).toRgba(); m_renderSettings.shadow = Color(command.substr(7)).toRgba();
} else if (command == "noshadow") { } else if (command == "noshadow") {
m_renderSettings.shadow = Color::Clear.toRgba(); m_renderSettings.shadow = Color::Clear.toRgba();
} else if (command.beginsWith("font=")) { } else if (command.beginsWith("font=")) {
m_renderSettings.font = command.substr(5); setFont(m_renderSettings.font = command.substr(5));
} else if (command.beginsWith("directives=")) { } else if (command.beginsWith("directives=")) {
setProcessingDirectives(command.substr(11)); setProcessingDirectives(command.substr(11));
} else if (command.beginsWith("backdirectives=")) { } else if (command.beginsWith("backdirectives=")) {
@ -404,6 +410,7 @@ RectF TextPainter::doRenderText(StringView s, TextPositioning const& position, b
} }
m_renderSettings = std::move(backup); m_renderSettings = std::move(backup);
m_fontTextureGroup.switchFont(m_renderSettings.font);
return bounds; return bounds;
} }
@ -455,7 +462,6 @@ RectF TextPainter::doRenderLine(StringView text, TextPositioning const& position
RectF TextPainter::doRenderGlyph(String::Char c, TextPositioning const& position, bool reallyRender) { RectF TextPainter::doRenderGlyph(String::Char c, TextPositioning const& position, bool reallyRender) {
if (c == '\n' || c == '\v' || c == '\r') if (c == '\n' || c == '\v' || c == '\r')
return RectF(); return RectF();
m_fontTextureGroup.switchFont(m_renderSettings.font);
int width = glyphWidth(c); int width = glyphWidth(c);
// Offset left by width if right anchored. // Offset left by width if right anchored.