Fix text after an unclosed ^ tag not wrapping
This commit is contained in:
parent
c24fc5aeaf
commit
dd67777238
@ -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"
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -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) {
|
||||||
|
@ -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; }
|
||||||
|
@ -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)
|
||||||
|
@ -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.
|
||||||
|
Loading…
Reference in New Issue
Block a user