From 5a56f8b81a10f3a91672055c3c6b7e9bde86efce Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Sat, 1 Jul 2023 23:20:25 +1000 Subject: [PATCH] Re-optimize text splitting while maintaining vanilla behavior --- source/core/StarColor.cpp | 2 +- source/core/StarStringView.cpp | 51 ++++++---- source/rendering/StarTextPainter.cpp | 139 +++++++++++++-------------- source/rendering/StarTextPainter.hpp | 3 +- 4 files changed, 97 insertions(+), 98 deletions(-) diff --git a/source/core/StarColor.cpp b/source/core/StarColor.cpp index d588752..0c10b9d 100644 --- a/source/core/StarColor.cpp +++ b/source/core/StarColor.cpp @@ -214,7 +214,7 @@ Color Color::gray(uint8_t g) { Color::Color() {} Color::Color(StringView name) { - if (name.beginsWith("#")) + if (name.beginsWith('#')) *this = fromHex(name.substr(1)); else { auto i = NamedColors.find(String(name).toLower()); diff --git a/source/core/StarStringView.cpp b/source/core/StarStringView.cpp index 8974e1a..0399b86 100644 --- a/source/core/StarStringView.cpp +++ b/source/core/StarStringView.cpp @@ -76,10 +76,15 @@ StringView::Char StringView::operator[](size_t index) const { return *it; } -StringView::Char StringView::at(size_t i) const { - if (i > size()) - throw OutOfRangeException(strf("Out of range in StringView::at({})", i)); - return operator[](i); +StringView::Char StringView::at(size_t index) const { + auto it = begin(); + auto itEnd = end(); + for (size_t i = 0; i < index; ++i) { + ++it; + if (it == itEnd) + throw OutOfRangeException(strf("Out of range in StringView::at({})", i)); + } + return *it; } bool StringView::endsWith(StringView end, CaseSensitivity cs) const { @@ -94,26 +99,28 @@ bool StringView::endsWith(StringView end, CaseSensitivity cs) const { return compare(mysize - endsize, NPos, end, 0, NPos, cs) == 0; } bool StringView::endsWith(Char end, CaseSensitivity cs) const { - if (size() == 0) + if (m_view.empty()) return false; return String::charEqual(end, operator[](size() - 1), cs); } bool StringView::beginsWith(StringView beg, CaseSensitivity cs) const { - auto begSize = beg.size(); - if (begSize == 0) + if (beg.m_view.empty()) return true; - auto mysize = size(); - if (begSize > mysize) - return false; + size_t begSize = beg.size(); + auto it = begin(); + auto itEnd = end(); + for (size_t i = 0; i != begSize; ++i) + if (it++ == itEnd) + return false; return compare(0, begSize, beg, 0, NPos, cs) == 0; } bool StringView::beginsWith(Char beg, CaseSensitivity cs) const { - if (size() == 0) + if (m_view.empty()) return false; return String::charEqual(beg, operator[](0), cs); @@ -288,8 +295,9 @@ size_t StringView::findFirstNotOf(StringView pattern, size_t beg) const { } size_t StringView::findNextBoundary(size_t index, bool backwards) const { - starAssert(index <= size()); - if (!backwards && (index == size())) + size_t mySize = size(); + starAssert(index <= mySize); + if (!backwards && (index == mySize)) return index; if (backwards) { if (index == 0) @@ -301,19 +309,19 @@ size_t StringView::findNextBoundary(size_t index, bool backwards) const { if (backwards && (index == 0)) return 0; index += backwards ? -1 : 1; - if (index == size()) - return size(); + if (index == mySize) + return mySize; c = this->at(index); } while (String::isSpace(c)) { if (backwards && (index == 0)) return 0; index += backwards ? -1 : 1; - if (index == size()) - return size(); + if (index == mySize) + return mySize; c = this->at(index); } - if (backwards && !(index == size())) + if (backwards && !(index == mySize)) return index + 1; return index; } @@ -339,17 +347,18 @@ bool StringView::equalsIgnoreCase(StringView s) const { StringView StringView::substr(size_t position, size_t n) const { StringView ret; - auto it_end = end(); + auto itEnd = end(); auto it = begin(); + for (size_t i = 0; i != position; ++i) { - if (++it == it_end) + if (it++ == itEnd) throw OutOfRangeException(strf("out of range in StringView::substr({}, {})", position, n)); } const char* start = &*it.base(); for (size_t i = 0; i != n; ++i) { - if (it++ == it_end) + if (it++ == itEnd) return StringView(start, &*it.base() - start - 1); } diff --git a/source/rendering/StarTextPainter.cpp b/source/rendering/StarTextPainter.cpp index c5c7084..e54ffd5 100644 --- a/source/rendering/StarTextPainter.cpp +++ b/source/rendering/StarTextPainter.cpp @@ -209,79 +209,88 @@ int TextPainter::stringWidth(StringView s) { return width; } -void TextPainter::processWrapText(StringView text, Maybe wrapWidth, WrapTextCallback textFunc, WrapCommandsCallback commandsFunc, bool includeCommandSides) { +bool TextPainter::processWrapText(StringView text, Maybe wrapWidth, WrapTextCallback textFunc) { String font = m_renderSettings.font, setFont = font; m_fontTextureGroup.switchFont(font); - - unsigned linePixelWidth = 0; // How wide is this line so far - unsigned lineCharSize = 0; // how many characters in this line ? - unsigned lineStart = 0; // Where does this line start ? - unsigned splitPos = 0; // Where did we last see a place to split the string ? - unsigned splitWidth = 0; // How wide was the string there ? - int lines = 0; StringView splitIgnore(m_splitIgnore); StringView splitForce(m_splitForce); - Text::CommandsCallback splitCommandsCallback = [&](StringView commands) { - StringView inner = commands.utf8().substr(1, commands.utf8Size() - 1); - inner.forEachSplitView(",", [&](StringView command, size_t, size_t) { - if (command == "reset") - m_fontTextureGroup.switchFont(font = setFont); - else if (command == "set") - setFont = font; - else if (command.beginsWith("font=")) - m_fontTextureGroup.switchFont(font = command.substr(5)); - }); - if (commandsFunc) - if (!commandsFunc(includeCommandSides ? commands : inner, lines)) - return false; + size_t i = 0; + auto it = text.begin(); + auto end = text.end(); + auto prevIt = it; - return true; + unsigned lineStart = 0; // Where does this line start ? + unsigned linePixelWidth = 0; // How wide is this line so far + unsigned lineCharSize = 0; // how many characters in this line ? + + auto escIt = end; + + unsigned splitPos = 0; // Where did we last see a place to split the string ? + unsigned splitWidth = 0; // How wide was the string there ? + + auto lineStartIt = it; // Where does this line start ? + auto splitIt = end; + + auto slice = [](StringView::const_iterator a, StringView::const_iterator b) -> StringView { + const char* aPtr = &*a.base(); + return StringView(aPtr, &*b.base() - aPtr); }; - Text::TextCallback splitTextCallback = [&](StringView sub) { - return textFunc(sub, lines); - }; + while (it != end) { + auto character = *it; - Text::CommandsCallback commandsCallback = [&](StringView commands) { - lineCharSize += commands.size(); - return true; - }; + if (Text::isEscapeCode(character)) + escIt = it; + ++i; + - Text::TextCallback textCallback = [&](StringView sub) { - for (auto character : sub) { + if (escIt != end) { + if (character == Text::EndEsc) { + StringView inner = slice(escIt, it); + inner.forEachSplitView(",", [&](StringView command, size_t, size_t) { + if (command == "reset") + m_fontTextureGroup.switchFont(font = setFont); + else if (command == "set") + setFont = font; + else if (command.beginsWith("font=")) + m_fontTextureGroup.switchFont(font = command.substr(5)); + }); + escIt = end; + } + lineCharSize++; + } else { lineCharSize++; // assume at least one character if we get here. // is this a linefeed / cr / whatever that forces a line split ? if (splitForce.find(character) != NPos) { // knock one off the end because we don't render the CR - if (!Text::processText(text.substr(lineStart, lineCharSize - 1), splitTextCallback, splitCommandsCallback, true)) + if (!textFunc(slice(lineStartIt, it), lines++)) return false; - ++lines; - lineStart += lineCharSize; // next line starts after the CR - lineCharSize = 0; // with no characters in it. - linePixelWidth = 0; // No width - splitPos = 0; // and no known splits. + lineStart += lineCharSize; + lineStartIt = it; + ++lineStartIt; // next line starts after the CR... + lineCharSize = linePixelWidth = splitPos = 0; // ...with no characters in it and no known splits. } else { int charWidth = glyphWidth(character); // is it a place where we might want to split the line ? if (splitIgnore.find(character) != NPos) { - splitPos = lineStart + lineCharSize; // this is the character after the space. + splitPos = lineStart + lineCharSize; splitWidth = linePixelWidth + charWidth; // the width of the string at + splitIt = it; // 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 (splitPos) { - if (!Text::processText(text.substr(lineStart, (splitPos - lineStart) - 1), splitTextCallback, splitCommandsCallback, true)) + if (splitIt != end) { + if (!textFunc(slice(lineStartIt, splitIt), lines++)) return false; - ++lines; unsigned stringEnd = lineStart + lineCharSize; lineCharSize = stringEnd - splitPos; // next line has the characters after the space. @@ -289,15 +298,19 @@ void TextPainter::processWrapText(StringView text, Maybe wrapWidth, Wr unsigned stringWidth = (linePixelWidth - splitWidth); linePixelWidth = stringWidth + charWidth; // and is as wide as the bit after the space. - lineStart = splitPos; // next line starts after the space - splitPos = 0; // split is used up. + lineStart = splitPos; + lineStartIt = splitIt; + splitIt = end; + splitPos = 0; } else { - if (!Text::processText(text.substr(lineStart, lineCharSize - 1), splitTextCallback, splitCommandsCallback, true)) + if (!textFunc(slice(lineStartIt, it), lines++)) return false; - ++lines; lineStart += lineCharSize - 1; // skip back by one to include that - // character on the next line. + // character on the next line. + lineStartIt = it; + --lineStartIt; + lineCharSize = 1; // next line has that character in linePixelWidth = charWidth; // and is as wide as that character } @@ -307,15 +320,14 @@ void TextPainter::processWrapText(StringView text, Maybe wrapWidth, Wr } } - return true; + prevIt = it++; }; - if (!Text::processText(text, textCallback, commandsCallback, true)) - return; - // if we hit the end of the string before hitting the end of the line. if (lineCharSize > 0) - Text::processText(text.substr(lineStart, lineCharSize), splitTextCallback, splitCommandsCallback, true); + return textFunc(slice(lineStartIt, end), lines); + + return true; } List TextPainter::wrapTextViews(StringView s, Maybe wrapWidth) { @@ -345,18 +357,7 @@ List TextPainter::wrapTextViews(StringView s, Maybe wrapWi return true; }; - TextPainter::WrapCommandsCallback commandsCallback = [&](StringView commands, int line) { - if (lastLine != line) { - views.push_back(current); - lastLine = line; - active = false; - } - - addText(commands); - return true; - }; - - processWrapText(s, wrapWidth, textCallback, commandsCallback, true); + processWrapText(s, wrapWidth, textCallback); if (active) views.push_back(current); @@ -379,17 +380,7 @@ StringList TextPainter::wrapText(StringView s, Maybe wrapWidth) { return true; }; - TextPainter::WrapCommandsCallback commandsCallback = [&](StringView commands, int line) { - if (lastLine != line) { - result.append(move(current)); - lastLine = line; - } - - current += commands; - return true; - }; - - processWrapText(s, wrapWidth, textCallback, commandsCallback, true); + processWrapText(s, wrapWidth, textCallback); if (!current.empty()) result.append(move(current)); diff --git a/source/rendering/StarTextPainter.hpp b/source/rendering/StarTextPainter.hpp index f1bd194..e006981 100644 --- a/source/rendering/StarTextPainter.hpp +++ b/source/rendering/StarTextPainter.hpp @@ -68,8 +68,7 @@ public: typedef function WrapTextCallback; - typedef function WrapCommandsCallback; - void processWrapText(StringView s, Maybe wrapWidth, WrapTextCallback textFunc, WrapCommandsCallback commandFunc = WrapCommandsCallback(), bool includeCommandSides = false); + bool processWrapText(StringView s, Maybe wrapWidth, WrapTextCallback textFunc); List wrapTextViews(StringView s, Maybe wrapWidth); StringList wrapText(StringView s, Maybe wrapWidth);