Re-optimize text splitting while maintaining vanilla behavior
This commit is contained in:
parent
e29a46d100
commit
5a56f8b81a
@ -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());
|
||||||
|
@ -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();
|
||||||
|
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));
|
throw OutOfRangeException(strf("Out of range in StringView::at({})", i));
|
||||||
return operator[](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();
|
||||||
|
auto itEnd = end();
|
||||||
|
for (size_t i = 0; i != begSize; ++i)
|
||||||
|
if (it++ == itEnd)
|
||||||
return false;
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,23 +209,47 @@ 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();
|
||||||
|
auto end = text.end();
|
||||||
|
auto prevIt = it;
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
while (it != end) {
|
||||||
|
auto character = *it;
|
||||||
|
|
||||||
|
if (Text::isEscapeCode(character))
|
||||||
|
escIt = it;
|
||||||
|
++i;
|
||||||
|
|
||||||
|
|
||||||
|
if (escIt != end) {
|
||||||
|
if (character == Text::EndEsc) {
|
||||||
|
StringView inner = slice(escIt, it);
|
||||||
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);
|
||||||
@ -234,54 +258,39 @@ void TextPainter::processWrapText(StringView text, Maybe<unsigned> wrapWidth, Wr
|
|||||||
else if (command.beginsWith("font="))
|
else if (command.beginsWith("font="))
|
||||||
m_fontTextureGroup.switchFont(font = command.substr(5));
|
m_fontTextureGroup.switchFont(font = command.substr(5));
|
||||||
});
|
});
|
||||||
if (commandsFunc)
|
escIt = end;
|
||||||
if (!commandsFunc(includeCommandSides ? commands : inner, lines))
|
}
|
||||||
return false;
|
lineCharSize++;
|
||||||
|
} else {
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
Text::TextCallback splitTextCallback = [&](StringView sub) {
|
|
||||||
return textFunc(sub, lines);
|
|
||||||
};
|
|
||||||
|
|
||||||
Text::CommandsCallback commandsCallback = [&](StringView commands) {
|
|
||||||
lineCharSize += commands.size();
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
Text::TextCallback textCallback = [&](StringView sub) {
|
|
||||||
for (auto character : sub) {
|
|
||||||
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));
|
||||||
|
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user