From dd677772382c3f63a5f2d331c6227e135e01db3c Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Tue, 23 Apr 2024 13:27:57 +1000 Subject: [PATCH] Fix text after an unclosed ^ tag not wrapping --- .../windowconfig/chatbubbles.config.patch | 4 +- source/core/StarText.cpp | 11 +- source/core/StarText.hpp | 2 + source/game/StarObject.cpp | 2 +- source/rendering/StarTextPainter.cpp | 150 +++++++++--------- 5 files changed, 87 insertions(+), 82 deletions(-) diff --git a/assets/opensb/interface/windowconfig/chatbubbles.config.patch b/assets/opensb/interface/windowconfig/chatbubbles.config.patch index 3c0fda8..c1773f6 100644 --- a/assets/opensb/interface/windowconfig/chatbubbles.config.patch +++ b/assets/opensb/interface/windowconfig/chatbubbles.config.patch @@ -1,7 +1,5 @@ { "movementThreshold" : 0.5, "bubbleOffset" : [0, 1.875], - "textStyle" : { - "backDirectives" : "?border=1;000a;0000?border=1;000a;0004" - } + "textStyle" : { "backDirectives" : "?border=1;111;1110?border=1;111;1117" } } \ No newline at end of file diff --git a/source/core/StarText.cpp b/source/core/StarText.cpp index 86c9f25..d507fd4 100644 --- a/source/core/StarText.cpp +++ b/source/core/StarText.cpp @@ -31,21 +31,20 @@ TextStyle& TextStyle::loadJson(Json const& config) { } 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)); String stripEscapeCodes(String const& s) { 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) { - std::string_view escChars(escapeChars); - std::string_view str = text.utf8(); while (true) { - size_t escape = str.find_first_of(escChars); + size_t escape = str.find_first_of(AllEsc); 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); if (end != NPos) { diff --git a/source/core/StarText.hpp b/source/core/StarText.hpp index ff6880c..15158ce 100644 --- a/source/core/StarText.hpp +++ b/source/core/StarText.hpp @@ -30,6 +30,8 @@ namespace Text { unsigned char const EndEsc = ';'; unsigned char const CmdEsc = '^'; unsigned char const SpecialCharLimit = ' '; + extern std::string const AllEsc; + extern std::string const AllEscEnd; String stripEscapeCodes(String const& s); inline bool isEscapeCode(Utf32Type c) { return c == CmdEsc || c == StartEsc; } diff --git a/source/game/StarObject.cpp b/source/game/StarObject.cpp index dd19268..fe1f3f3 100644 --- a/source/game/StarObject.cpp +++ b/source/game/StarObject.cpp @@ -411,7 +411,7 @@ void Object::render(RenderCallback* renderCallback) { renderCallback->addAudios(m_networkedAnimatorDynamicTarget.pullNewAudios()); 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()); } else { if (m_orientationIndex != NPos) diff --git a/source/rendering/StarTextPainter.cpp b/source/rendering/StarTextPainter.cpp index 19e48f1..fa90208 100644 --- a/source/rendering/StarTextPainter.cpp +++ b/source/rendering/StarTextPainter.cpp @@ -130,34 +130,39 @@ int TextPainter::stringWidth(StringView s, unsigned charLimit) { bool TextPainter::processWrapText(StringView text, unsigned* wrapWidth, WrapTextCallback textFunc) { String font = m_renderSettings.font, setFont = font; m_fontTextureGroup.switchFont(font); + auto iterator = text.begin(), end = text.end(); + unsigned lines = 0; - - auto it = text.begin(); - auto end = text.end(); - - 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 lineStartIterator = iterator, splitIterator = end; + unsigned linePixelWidth = 0, splitPixelWidth = 0; + size_t commandStart = NPos, commandEnd = NPos; + bool finished = true; auto slice = [](StringView::const_iterator a, StringView::const_iterator b) -> StringView { const char* aPtr = &*a.base(); return StringView(aPtr, &*b.base() - aPtr); }; - while (it != end) { - auto character = *it; - - if (Text::isEscapeCode(character)) - escIt = it; - - if (escIt != end) { - if (character == Text::EndEsc) { - StringView inner = slice(++escIt, it); + while (iterator != end) { + auto character = *iterator; + finished = false; // assume at least one character if we get here + bool noMoreCommands = commandStart != NPos && commandEnd == NPos; + if (!noMoreCommands && Text::isEscapeCode(character)) { + size_t index = &*iterator.base() - text.utf8Ptr(); + if (commandStart == NPos) { + for (size_t escOrEnd = commandStart = index; + (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) { if (command == "reset") { m_fontTextureGroup.switchFont(font = setFont); @@ -167,63 +172,61 @@ bool TextPainter::processWrapText(StringView text, unsigned* wrapWidth, WrapText 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 { - 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 ? - if (character == '\n' || character == '\v') { - // knock one off the end because we don't render the CR - if (!textFunc(slice(lineStartIt, it), lines++)) - return false; - - lineStartIt = it; - ++lineStartIt; - // next line starts after the CR with no characters in it and no known splits. - lineCharSize = linePixelWidth = 0; - splitIt = 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 - } + // would the line be too long if we render this next character ? + if (wrapWidth && (linePixelWidth + characterWidth) > *wrapWidth) { + // did we find somewhere to split the line ? + if (splitIterator != end) { + if (!textFunc(slice(lineStartIterator, splitIterator), lines++)) + return false; + // do not include the split character on the next line + unsigned stringWidth = linePixelWidth - splitPixelWidth; + linePixelWidth = stringWidth + characterWidth; + lineStartIterator = ++splitIterator; + splitIterator = end; } 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 (lineCharSize > 0) - return textFunc(slice(lineStartIt, end), lines); - - return true; + // if we hit the end of the string before hitting the end of the line + return finished || textFunc(slice(lineStartIterator, end), lines); } List TextPainter::wrapTextViews(StringView s, Maybe wrapWidth) { @@ -293,18 +296,20 @@ void TextPainter::setProcessingDirectives(StringView directives, bool back) { } void TextPainter::setFont(String const& font) { - m_renderSettings.font = font; + m_fontTextureGroup.switchFont(m_renderSettings.font = font); } TextStyle& TextPainter::setTextStyle(TextStyle const& textStyle) { TextStyle& style = m_renderSettings = textStyle; modifyDirectives(style.directives); modifyDirectives(style.backDirectives); + m_fontTextureGroup.switchFont(style.font); return style; } void TextPainter::clearTextStyle() { m_renderSettings = m_defaultRenderSettings; + m_fontTextureGroup.switchFont(m_renderSettings.font); } void TextPainter::addFont(FontPtr const& font, String const& name) { @@ -343,17 +348,18 @@ void TextPainter::applyCommands(StringView unsplitCommands) { try { if (command == "reset") { m_renderSettings = m_savedRenderSettings; + m_fontTextureGroup.switchFont(m_renderSettings.font); } else if (command == "set") { m_savedRenderSettings = m_renderSettings; } else if (command.beginsWith("shadow")) { if (command.utf8Size() == 6) m_renderSettings.shadow = Color::Black.toRgba(); - else if (command[6] == '=') + else if (command.utf8()[6] == '=') m_renderSettings.shadow = Color(command.substr(7)).toRgba(); } else if (command == "noshadow") { m_renderSettings.shadow = Color::Clear.toRgba(); } else if (command.beginsWith("font=")) { - m_renderSettings.font = command.substr(5); + setFont(m_renderSettings.font = command.substr(5)); } else if (command.beginsWith("directives=")) { setProcessingDirectives(command.substr(11)); } else if (command.beginsWith("backdirectives=")) { @@ -404,6 +410,7 @@ RectF TextPainter::doRenderText(StringView s, TextPositioning const& position, b } m_renderSettings = std::move(backup); + m_fontTextureGroup.switchFont(m_renderSettings.font); 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) { if (c == '\n' || c == '\v' || c == '\r') return RectF(); - m_fontTextureGroup.switchFont(m_renderSettings.font); int width = glyphWidth(c); // Offset left by width if right anchored.