Re-optimize text splitting while maintaining vanilla behavior

This commit is contained in:
Kae 2023-07-01 23:20:25 +10:00
parent e29a46d100
commit 5a56f8b81a
4 changed files with 97 additions and 98 deletions

View File

@ -214,7 +214,7 @@ Color Color::gray(uint8_t g) {
Color::Color() {} Color::Color() {}
Color::Color(StringView name) { Color::Color(StringView name) {
if (name.beginsWith("#")) if (name.beginsWith('#'))
*this = fromHex(name.substr(1)); *this = fromHex(name.substr(1));
else { else {
auto i = NamedColors.find(String(name).toLower()); auto i = NamedColors.find(String(name).toLower());

View File

@ -76,10 +76,15 @@ StringView::Char StringView::operator[](size_t index) const {
return *it; return *it;
} }
StringView::Char StringView::at(size_t i) const { StringView::Char StringView::at(size_t index) const {
if (i > size()) auto it = begin();
throw OutOfRangeException(strf("Out of range in StringView::at({})", i)); auto itEnd = end();
return operator[](i); 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 { 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; return compare(mysize - endsize, NPos, end, 0, NPos, cs) == 0;
} }
bool StringView::endsWith(Char end, CaseSensitivity cs) const { bool StringView::endsWith(Char end, CaseSensitivity cs) const {
if (size() == 0) if (m_view.empty())
return false; return false;
return String::charEqual(end, operator[](size() - 1), cs); return String::charEqual(end, operator[](size() - 1), cs);
} }
bool StringView::beginsWith(StringView beg, CaseSensitivity cs) const { bool StringView::beginsWith(StringView beg, CaseSensitivity cs) const {
auto begSize = beg.size(); if (beg.m_view.empty())
if (begSize == 0)
return true; return true;
auto mysize = size(); size_t begSize = beg.size();
if (begSize > mysize) auto it = begin();
return false; 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; return compare(0, begSize, beg, 0, NPos, cs) == 0;
} }
bool StringView::beginsWith(Char beg, CaseSensitivity cs) const { bool StringView::beginsWith(Char beg, CaseSensitivity cs) const {
if (size() == 0) if (m_view.empty())
return false; return false;
return String::charEqual(beg, operator[](0), cs); 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 { size_t StringView::findNextBoundary(size_t index, bool backwards) const {
starAssert(index <= size()); size_t mySize = size();
if (!backwards && (index == size())) starAssert(index <= mySize);
if (!backwards && (index == mySize))
return index; return index;
if (backwards) { if (backwards) {
if (index == 0) if (index == 0)
@ -301,19 +309,19 @@ size_t StringView::findNextBoundary(size_t index, bool backwards) const {
if (backwards && (index == 0)) if (backwards && (index == 0))
return 0; return 0;
index += backwards ? -1 : 1; index += backwards ? -1 : 1;
if (index == size()) if (index == mySize)
return size(); return mySize;
c = this->at(index); c = this->at(index);
} }
while (String::isSpace(c)) { while (String::isSpace(c)) {
if (backwards && (index == 0)) if (backwards && (index == 0))
return 0; return 0;
index += backwards ? -1 : 1; index += backwards ? -1 : 1;
if (index == size()) if (index == mySize)
return size(); return mySize;
c = this->at(index); c = this->at(index);
} }
if (backwards && !(index == size())) if (backwards && !(index == mySize))
return index + 1; return index + 1;
return index; return index;
} }
@ -339,17 +347,18 @@ bool StringView::equalsIgnoreCase(StringView s) const {
StringView StringView::substr(size_t position, size_t n) const { StringView StringView::substr(size_t position, size_t n) const {
StringView ret; StringView ret;
auto it_end = end(); auto itEnd = end();
auto it = begin(); auto it = begin();
for (size_t i = 0; i != position; ++i) { 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)); throw OutOfRangeException(strf("out of range in StringView::substr({}, {})", position, n));
} }
const char* start = &*it.base(); const char* start = &*it.base();
for (size_t i = 0; i != n; ++i) { for (size_t i = 0; i != n; ++i) {
if (it++ == it_end) if (it++ == itEnd)
return StringView(start, &*it.base() - start - 1); return StringView(start, &*it.base() - start - 1);
} }

View File

@ -209,79 +209,88 @@ int TextPainter::stringWidth(StringView s) {
return width; return width;
} }
void TextPainter::processWrapText(StringView text, Maybe<unsigned> wrapWidth, WrapTextCallback textFunc, WrapCommandsCallback commandsFunc, bool includeCommandSides) { bool TextPainter::processWrapText(StringView text, Maybe<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);
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; int lines = 0;
StringView splitIgnore(m_splitIgnore); StringView splitIgnore(m_splitIgnore);
StringView splitForce(m_splitForce); StringView splitForce(m_splitForce);
Text::CommandsCallback splitCommandsCallback = [&](StringView commands) { size_t i = 0;
StringView inner = commands.utf8().substr(1, commands.utf8Size() - 1); auto it = text.begin();
inner.forEachSplitView(",", [&](StringView command, size_t, size_t) { auto end = text.end();
if (command == "reset") auto prevIt = it;
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;
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) { while (it != end) {
return textFunc(sub, lines); auto character = *it;
};
Text::CommandsCallback commandsCallback = [&](StringView commands) { if (Text::isEscapeCode(character))
lineCharSize += commands.size(); escIt = it;
return true; ++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. lineCharSize++; // assume at least one character if we get here.
// is this a linefeed / cr / whatever that forces a line split ? // is this a linefeed / cr / whatever that forces a line split ?
if (splitForce.find(character) != NPos) { if (splitForce.find(character) != NPos) {
// knock one off the end because we don't render the CR // 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; return false;
++lines;
lineStart += lineCharSize; // next line starts after the CR lineStart += lineCharSize;
lineCharSize = 0; // with no characters in it. lineStartIt = it;
linePixelWidth = 0; // No width ++lineStartIt; // next line starts after the CR...
splitPos = 0; // and no known splits. lineCharSize = linePixelWidth = splitPos = 0; // ...with no characters in it and no known splits.
} else { } else {
int charWidth = glyphWidth(character); int charWidth = glyphWidth(character);
// is it a place where we might want to split the line ? // is it a place where we might want to split the line ?
if (splitIgnore.find(character) != NPos) { 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 splitWidth = linePixelWidth + charWidth; // the width of the string at
splitIt = it;
// the split point, i.e. after the space. // the split point, i.e. after the space.
} }
// would the line be too long if we render this next character ? // would the line be too long if we render this next character ?
if (wrapWidth && (linePixelWidth + charWidth) > *wrapWidth) { if (wrapWidth && (linePixelWidth + charWidth) > *wrapWidth) {
// did we find somewhere to split the line ? // did we find somewhere to split the line ?
if (splitPos) { if (splitIt != end) {
if (!Text::processText(text.substr(lineStart, (splitPos - lineStart) - 1), splitTextCallback, splitCommandsCallback, true)) if (!textFunc(slice(lineStartIt, splitIt), lines++))
return false; return false;
++lines;
unsigned stringEnd = lineStart + lineCharSize; unsigned stringEnd = lineStart + lineCharSize;
lineCharSize = stringEnd - splitPos; // next line has the characters after the space. lineCharSize = stringEnd - splitPos; // next line has the characters after the space.
@ -289,15 +298,19 @@ void TextPainter::processWrapText(StringView text, Maybe<unsigned> wrapWidth, Wr
unsigned stringWidth = (linePixelWidth - splitWidth); unsigned stringWidth = (linePixelWidth - splitWidth);
linePixelWidth = stringWidth + charWidth; // and is as wide as the bit after the space. linePixelWidth = stringWidth + charWidth; // and is as wide as the bit after the space.
lineStart = splitPos; // next line starts after the space lineStart = splitPos;
splitPos = 0; // split is used up. lineStartIt = splitIt;
splitIt = end;
splitPos = 0;
} else { } else {
if (!Text::processText(text.substr(lineStart, lineCharSize - 1), splitTextCallback, splitCommandsCallback, true)) if (!textFunc(slice(lineStartIt, it), lines++))
return false; return false;
++lines;
lineStart += lineCharSize - 1; // skip back by one to include that 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 lineCharSize = 1; // next line has that character in
linePixelWidth = charWidth; // and is as wide as that character linePixelWidth = charWidth; // and is as wide as that character
} }
@ -307,15 +320,14 @@ void TextPainter::processWrapText(StringView text, Maybe<unsigned> 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 we hit the end of the string before hitting the end of the line.
if (lineCharSize > 0) if (lineCharSize > 0)
Text::processText(text.substr(lineStart, lineCharSize), splitTextCallback, splitCommandsCallback, true); 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) {
@ -345,18 +357,7 @@ List<StringView> TextPainter::wrapTextViews(StringView s, Maybe<unsigned> wrapWi
return true; return true;
}; };
TextPainter::WrapCommandsCallback commandsCallback = [&](StringView commands, int line) { processWrapText(s, wrapWidth, textCallback);
if (lastLine != line) {
views.push_back(current);
lastLine = line;
active = false;
}
addText(commands);
return true;
};
processWrapText(s, wrapWidth, textCallback, commandsCallback, true);
if (active) if (active)
views.push_back(current); views.push_back(current);
@ -379,17 +380,7 @@ StringList TextPainter::wrapText(StringView s, Maybe<unsigned> wrapWidth) {
return true; return true;
}; };
TextPainter::WrapCommandsCallback commandsCallback = [&](StringView commands, int line) { processWrapText(s, wrapWidth, textCallback);
if (lastLine != line) {
result.append(move(current));
lastLine = line;
}
current += commands;
return true;
};
processWrapText(s, wrapWidth, textCallback, commandsCallback, true);
if (!current.empty()) if (!current.empty())
result.append(move(current)); result.append(move(current));

View File

@ -68,8 +68,7 @@ public:
typedef function<bool(StringView, int)> WrapTextCallback; typedef function<bool(StringView, int)> WrapTextCallback;
typedef function<bool(StringView, int)> WrapCommandsCallback; bool processWrapText(StringView s, Maybe<unsigned> wrapWidth, WrapTextCallback textFunc);
void processWrapText(StringView s, Maybe<unsigned> wrapWidth, WrapTextCallback textFunc, WrapCommandsCallback commandFunc = WrapCommandsCallback(), bool includeCommandSides = false);
List<StringView> wrapTextViews(StringView s, Maybe<unsigned> wrapWidth); List<StringView> wrapTextViews(StringView s, Maybe<unsigned> wrapWidth);
StringList wrapText(StringView s, Maybe<unsigned> wrapWidth); StringList wrapText(StringView s, Maybe<unsigned> wrapWidth);