Rewrite of the text wrapping code to work with languages which generally lack spaces, like Japanese.

Also implemented massive performance improvements to the text wrapping code.
This commit is contained in:
Leon Styhre 2022-10-08 09:33:57 +02:00
parent 13a603a687
commit 508ea87963
8 changed files with 214 additions and 321 deletions

View file

@ -296,7 +296,7 @@ void GuiMenu::openUIOptions()
it != SystemData::sSystemVector.cend(); ++it) { it != SystemData::sSystemVector.cend(); ++it) {
if ((*it)->getName() != "retropie") { if ((*it)->getName() != "retropie") {
// If required, abbreviate the system name so it doesn't overlap the setting name. // If required, abbreviate the system name so it doesn't overlap the setting name.
float maxNameLength {mSize.x * 0.48f}; float maxNameLength {mSize.x * 0.51f};
startupSystem->add((*it)->getFullName(), (*it)->getName(), startupSystem->add((*it)->getFullName(), (*it)->getName(),
Settings::getInstance()->getString("StartupSystem") == Settings::getInstance()->getString("StartupSystem") ==
(*it)->getName(), (*it)->getName(),

View file

@ -342,26 +342,8 @@ private:
// A maximum length parameter has been passed and the "name" size surpasses // A maximum length parameter has been passed and the "name" size surpasses
// this value, so abbreviate the string inside the arrows. // this value, so abbreviate the string inside the arrows.
auto font = Font::get(FONT_SIZE_MEDIUM); auto font = Font::get(FONT_SIZE_MEDIUM);
// Calculate with an extra dot to give some leeway. mText.setText(Utils::String::toUpper(
float dotsSize {font->sizeText("....").x}; font->wrapText(Utils::String::toUpper(it->name), it->maxNameLength)));
std::string abbreviatedString {font->getTextMaxWidth(
Utils::String::toUpper(it->name), it->maxNameLength)};
float sizeDifference {font->sizeText(Utils::String::toUpper(it->name)).x -
font->sizeText(abbreviatedString).x};
if (sizeDifference > 0.0f) {
// It doesn't make sense to abbreviate if the number of pixels removed
// by the abbreviation is less or equal to the size of the three dots
// that would be appended to the string.
if (sizeDifference <= dotsSize) {
abbreviatedString = it->name;
}
else {
if (abbreviatedString.back() == ' ')
abbreviatedString.pop_back();
abbreviatedString += "...";
}
}
mText.setText(Utils::String::toUpper(abbreviatedString));
} }
else { else {
mText.setText(Utils::String::toUpper(it->name)); mText.setText(Utils::String::toUpper(it->name));

View file

@ -236,57 +236,14 @@ void TextComponent::render(const glm::mat4& parentTrans)
} }
} }
void TextComponent::calculateExtent()
{
if (mAutoCalcExtent.x) {
if (mUppercase)
mSize = mFont->sizeText(Utils::String::toUpper(mText), mLineSpacing);
else if (mLowercase)
mSize = mFont->sizeText(Utils::String::toLower(mText), mLineSpacing);
else if (mCapitalize)
mSize = mFont->sizeText(Utils::String::toCapitalized(mText), mLineSpacing);
else
mSize = mFont->sizeText(mText, mLineSpacing); // Original case.
}
else {
if (mAutoCalcExtent.y) {
if (mUppercase) {
mSize.y =
mFont->sizeWrappedText(Utils::String::toUpper(mText), getSize().x, mLineSpacing)
.y;
}
else if (mLowercase) {
mSize.y =
mFont->sizeWrappedText(Utils::String::toLower(mText), getSize().x, mLineSpacing)
.y;
}
else if (mCapitalize) {
mSize.y = mFont
->sizeWrappedText(Utils::String::toCapitalized(mText), getSize().x,
mLineSpacing)
.y;
}
else {
mSize.y = mFont->sizeWrappedText(mText, getSize().x, mLineSpacing).y;
}
}
}
}
void TextComponent::onTextChanged() void TextComponent::onTextChanged()
{ {
if (!mVerticalAutoSizing) if (!mVerticalAutoSizing)
mVerticalAutoSizing = (mSize.x != 0.0f && mSize.y == 0.0f); mVerticalAutoSizing = (mSize.x != 0.0f && mSize.y == 0.0f);
calculateExtent();
if (!mFont || mText.empty() || mSize.x == 0.0f || mSize.y == 0.0f) {
mTextCache.reset();
return;
}
std::string text; std::string text;
if (mText != "") {
if (mUppercase) if (mUppercase)
text = Utils::String::toUpper(mText); text = Utils::String::toUpper(mText);
else if (mLowercase) else if (mLowercase)
@ -295,50 +252,35 @@ void TextComponent::onTextChanged()
text = Utils::String::toCapitalized(mText); text = Utils::String::toCapitalized(mText);
else else
text = mText; // Original case. text = mText; // Original case.
}
std::shared_ptr<Font> f {mFont}; if (mFont && mAutoCalcExtent.x)
const float lineHeight {f->getHeight(mLineSpacing)}; mSize = mFont->sizeText(text, mLineSpacing);
const bool isMultiline {mSize.y > lineHeight};
if (!mFont || text.empty() || mSize.x < 0.0f)
return;
std::shared_ptr<Font> font {mFont};
const float lineHeight {font->getHeight(mLineSpacing)};
const bool isMultiline {mAutoCalcExtent.y == 1 || mSize.y > lineHeight};
const bool isScrollable {mParent && mParent->isScrollable()}; const bool isScrollable {mParent && mParent->isScrollable()};
bool addAbbrev {false}; if (isMultiline && text.size() && !isScrollable) {
if (!isMultiline) { const std::string wrappedText {
size_t newline {text.find('\n')}; font->wrapText(text, mSize.x, (mVerticalAutoSizing ? 0.0f : mSize.y - lineHeight),
// Single line of text - stop at the first newline since it'll mess everything up. mLineSpacing, isMultiline)};
text = text.substr(0, newline);
addAbbrev = newline != std::string::npos;
}
glm::vec2 size {f->sizeText(text)};
if (!isMultiline && text.size() && (size.x > mSize.x || addAbbrev)) {
// Abbreviate text.
const std::string abbrev {"..."};
float abbrevSize {f->sizeText(abbrev).x};
while (text.size() && size.x + abbrevSize > mSize.x) {
size_t newSize {Utils::String::prevCursor(text, text.size())};
text.erase(newSize, text.size() - newSize);
if (!text.empty() && text.back() == ' ')
text.pop_back();
size = f->sizeText(text);
}
text.append(abbrev);
mTextCache = std::shared_ptr<TextCache>(f->buildTextCache(
text, glm::vec2 {}, mColor, mSize.x, mHorizontalAlignment, mLineSpacing, mNoTopMargin));
}
else if (isMultiline && text.size() && !isScrollable) {
const std::string wrappedText {f->wrapText(
text, mSize.x, (mVerticalAutoSizing ? 0.0f : mSize.y - lineHeight), mLineSpacing)};
mTextCache = std::shared_ptr<TextCache>(f->buildTextCache(wrappedText, glm::vec2 {}, mColor,
mSize.x, mHorizontalAlignment,
mLineSpacing, mNoTopMargin));
}
else {
mTextCache = std::shared_ptr<TextCache>( mTextCache = std::shared_ptr<TextCache>(
f->buildTextCache(f->wrapText(text, mSize.x), glm::vec2 {0.0f, 0.0f}, mColor, mSize.x, font->buildTextCache(wrappedText, glm::vec2 {0.0f, 0.0f}, mColor, mSize.x,
mHorizontalAlignment, mLineSpacing, mNoTopMargin)); mHorizontalAlignment, mLineSpacing, mNoTopMargin));
} }
else {
mTextCache = std::shared_ptr<TextCache>(font->buildTextCache(
font->wrapText(text, mSize.x, 0.0f, mLineSpacing, isMultiline), glm::vec2 {0.0f, 0.0f},
mColor, mSize.x, mHorizontalAlignment, mLineSpacing, mNoTopMargin));
}
if (mAutoCalcExtent.y)
mSize.y = font->getTextSize().y;
if (mOpacity != 1.0f || mThemeOpacity != 1.0f) if (mOpacity != 1.0f || mThemeOpacity != 1.0f)
setOpacity(mOpacity); setOpacity(mOpacity);

View file

@ -14,13 +14,10 @@
class ThemeData; class ThemeData;
// Used to display text. // TextComponent sizing works in the following ways:
// TextComponent::setSize(x, y) works a little differently than most components: // setSize(0.0f, 0.0f) - Automatically sizes single-line text by expanding horizontally.
// * (0, 0) - Will automatically calculate a size that fits // setSize(width, 0.0f) - Limits size horizontally and automatically expands vertically.
// the text on one line (expand horizontally). // setSize(width, height) - Wraps and abbreviates text inside the width and height boundaries.
// * (x != 0, 0) - Wrap text so that it does not reach beyond x. Will
// automatically calculate a vertical size (expand vertically).
// * (x != 0, y <= fontHeight) - Will truncate text so it fits within this box.
class TextComponent : public GuiComponent class TextComponent : public GuiComponent
{ {
public: public:
@ -89,7 +86,6 @@ protected:
std::shared_ptr<Font> mFont; std::shared_ptr<Font> mFont;
private: private:
void calculateExtent();
void onColorChanged(); void onColorChanged();
static inline std::vector<std::string> supportedSystemdataTypes { static inline std::vector<std::string> supportedSystemdataTypes {

View file

@ -27,6 +27,7 @@ TextEditComponent::TextEditComponent()
, mBlinkTime {0} , mBlinkTime {0}
, mCursorRepeatDir {0} , mCursorRepeatDir {0}
, mScrollOffset {0.0f, 0.0f} , mScrollOffset {0.0f, 0.0f}
, mCursorPos {0.0f, 0.0f}
, mBox {":/graphics/textinput.svg"} , mBox {":/graphics/textinput.svg"}
, mFont {Font::get(FONT_SIZE_MEDIUM, FONT_PATH_LIGHT)} , mFont {Font::get(FONT_SIZE_MEDIUM, FONT_PATH_LIGHT)}
{ {
@ -51,6 +52,9 @@ void TextEditComponent::onFocusLost()
void TextEditComponent::onSizeChanged() void TextEditComponent::onSizeChanged()
{ {
if (mSize.x == 0.0f || mSize.y == 0.0f)
return;
mBox.fitTo( mBox.fitTo(
mSize, glm::vec3 {}, mSize, glm::vec3 {},
glm::vec2 {-34.0f, -32.0f - (TEXT_PADDING_VERT * Renderer::getScreenHeightModifier())}); glm::vec2 {-34.0f, -32.0f - (TEXT_PADDING_VERT * Renderer::getScreenHeightModifier())});
@ -263,9 +267,10 @@ void TextEditComponent::setCursor(size_t pos)
void TextEditComponent::onTextChanged() void TextEditComponent::onTextChanged()
{ {
std::string wrappedText = (isMultiline() ? mFont->wrapText(mText, getTextAreaSize().x) : mText); mWrappedText =
(isMultiline() ? mFont->wrapText(mText, getTextAreaSize().x, 0.0f, 1.5f, true) : mText);
mTextCache = std::unique_ptr<TextCache>(mFont->buildTextCache( mTextCache = std::unique_ptr<TextCache>(mFont->buildTextCache(
wrappedText, 0.0f, 0.0f, 0x77777700 | static_cast<unsigned char>(mOpacity * 255.0f))); mWrappedText, 0.0f, 0.0f, 0x77777700 | static_cast<unsigned char>(mOpacity * 255.0f)));
if (mCursor > static_cast<int>(mText.length())) if (mCursor > static_cast<int>(mText.length()))
mCursor = static_cast<int>(mText.length()); mCursor = static_cast<int>(mText.length());
@ -274,22 +279,23 @@ void TextEditComponent::onTextChanged()
void TextEditComponent::onCursorChanged() void TextEditComponent::onCursorChanged()
{ {
if (isMultiline()) { if (isMultiline()) {
glm::vec2 textSize {mFont->getWrappedTextCursorOffset(mText, getTextAreaSize().x, mCursor)}; mCursorPos = mFont->getWrappedTextCursorOffset(mWrappedText, mCursor);
// Need to scroll down? // Need to scroll down?
if (mScrollOffset.y + getTextAreaSize().y < textSize.y + mFont->getHeight()) if (mScrollOffset.y + getTextAreaSize().y < mCursorPos.y + mFont->getHeight())
mScrollOffset.y = textSize.y - getTextAreaSize().y + mFont->getHeight(); mScrollOffset.y = mCursorPos.y - getTextAreaSize().y + mFont->getHeight();
// Need to scroll up? // Need to scroll up?
else if (mScrollOffset.y > textSize.y) else if (mScrollOffset.y > mCursorPos.y)
mScrollOffset.y = textSize.y; mScrollOffset.y = mCursorPos.y;
} }
else { else {
glm::vec2 cursorPos {mFont->sizeText(mText.substr(0, mCursor))}; mCursorPos = mFont->sizeText(mText.substr(0, mCursor));
mCursorPos.y = 0.0f;
if (mScrollOffset.x + getTextAreaSize().x < cursorPos.x) if (mScrollOffset.x + getTextAreaSize().x < mCursorPos.x)
mScrollOffset.x = cursorPos.x - getTextAreaSize().x; mScrollOffset.x = mCursorPos.x - getTextAreaSize().x;
else if (mScrollOffset.x > cursorPos.x) else if (mScrollOffset.x > mCursorPos.x)
mScrollOffset.x = cursorPos.x; mScrollOffset.x = mCursorPos.x;
} }
} }
@ -323,25 +329,16 @@ void TextEditComponent::render(const glm::mat4& parentTrans)
mRenderer->popClipRect(); mRenderer->popClipRect();
// Draw cursor. // Draw cursor.
glm::vec2 cursorPos; float cursorHeight {mFont->getHeight() * 0.8f};
if (isMultiline()) {
cursorPos = mFont->getWrappedTextCursorOffset(mText, getTextAreaSize().x, mCursor);
}
else {
cursorPos = mFont->sizeText(mText.substr(0, mCursor));
cursorPos[1] = 0;
}
float cursorHeight = mFont->getHeight() * 0.8f;
if (!mEditing) { if (!mEditing) {
mRenderer->drawRect(cursorPos.x, cursorPos.y + (mFont->getHeight() - cursorHeight) / 2.0f, mRenderer->drawRect(mCursorPos.x, mCursorPos.y + (mFont->getHeight() - cursorHeight) / 2.0f,
2.0f * Renderer::getScreenWidthModifier(), cursorHeight, 0xC7C7C7FF, 2.0f * Renderer::getScreenWidthModifier(), cursorHeight, 0xC7C7C7FF,
0xC7C7C7FF); 0xC7C7C7FF);
} }
if (mEditing && mBlinkTime < BLINKTIME / 2) { if (mEditing && mBlinkTime < BLINKTIME / 2) {
mRenderer->drawRect(cursorPos.x, cursorPos.y + (mFont->getHeight() - cursorHeight) / 2.0f, mRenderer->drawRect(mCursorPos.x, mCursorPos.y + (mFont->getHeight() - cursorHeight) / 2.0f,
2.0f * Renderer::getScreenWidthModifier(), cursorHeight, 0x777777FF, 2.0f * Renderer::getScreenWidthModifier(), cursorHeight, 0x777777FF,
0x777777FF); 0x777777FF);
} }

View file

@ -59,6 +59,7 @@ private:
Renderer* mRenderer; Renderer* mRenderer;
std::string mText; std::string mText;
std::string mWrappedText;
std::string mTextOrig; std::string mTextOrig;
bool mFocused; bool mFocused;
bool mEditing; bool mEditing;
@ -70,6 +71,7 @@ private:
int mCursorRepeatDir; int mCursorRepeatDir;
glm::vec2 mScrollOffset; glm::vec2 mScrollOffset;
glm::vec2 mCursorPos;
NinePatchComponent mBox; NinePatchComponent mBox;

View file

@ -20,16 +20,17 @@ std::map<std::pair<std::string, int>, std::weak_ptr<Font>> Font::sFontMap;
Font::Font(int size, const std::string& path) Font::Font(int size, const std::string& path)
: mRenderer {Renderer::getInstance()} : mRenderer {Renderer::getInstance()}
, mSize(size) , mFontSize(size)
, mMaxGlyphHeight {0} , mMaxGlyphHeight {0}
, mTextSize {0.0f, 0.0f}
, mPath(path) , mPath(path)
{ {
if (mSize < 9) { if (mFontSize < 9) {
mSize = 9; mFontSize = 9;
LOG(LogWarning) << "Requested font size too small, changing to minimum supported size"; LOG(LogWarning) << "Requested font size too small, changing to minimum supported size";
} }
else if (mSize > Renderer::getScreenHeight()) { else if (mFontSize > Renderer::getScreenHeight()) {
mSize = static_cast<int>(Renderer::getScreenHeight()); mFontSize = static_cast<int>(Renderer::getScreenHeight());
LOG(LogWarning) << "Requested font size too large, changing to maximum supported size"; LOG(LogWarning) << "Requested font size too large, changing to maximum supported size";
} }
@ -47,7 +48,7 @@ Font::~Font()
{ {
unload(ResourceManager::getInstance()); unload(ResourceManager::getInstance());
auto fontEntry = sFontMap.find(std::pair<std::string, int>(mPath, mSize)); auto fontEntry = sFontMap.find(std::pair<std::string, int>(mPath, mFontSize));
if (fontEntry != sFontMap.cend()) if (fontEntry != sFontMap.cend())
sFontMap.erase(fontEntry); sFontMap.erase(fontEntry);
@ -287,186 +288,157 @@ void Font::renderTextCache(TextCache* cache)
} }
} }
std::string Font::wrapText(std::string text, float maxLength, float maxHeight, float lineSpacing) std::string Font::wrapText(const std::string& text,
const float maxLength,
const float maxHeight,
const float lineSpacing,
const bool multiLine)
{ {
assert(maxLength != 0.0f); assert(maxLength > 0.0f);
std::string out;
std::string line;
std::string word;
std::string abbreviatedWord;
std::string temp;
size_t space {0};
glm::vec2 textSize {0.0f, 0.0f};
const float dotsSize {sizeText("...").x};
const float lineHeight {getHeight(lineSpacing)}; const float lineHeight {getHeight(lineSpacing)};
float accumHeight {0.0f}; const float dotsWidth {sizeText("...").x};
const bool restrictHeight {maxHeight > 0.0f}; float accumHeight {lineHeight};
bool skipLastLine {false};
float currLineLength {0.0f};
// While there's text or we still have text to render.
while (text.length() > 0) {
if (restrictHeight && accumHeight > maxHeight)
break;
space = text.find_first_of(" \t\n");
if (space == std::string::npos) {
space = text.length() - 1;
}
else if (restrictHeight) {
if (text.at(space) == '\n')
accumHeight += lineHeight;
}
word = text.substr(0, space + 1);
text.erase(0, space + 1);
temp = line + word;
textSize = sizeText(temp);
// If the word will fit on the line, add it to our line and continue.
if (textSize.x <= maxLength) {
line = temp;
currLineLength = textSize.x;
continue;
}
else {
// If the word is too long to fit within maxLength then abbreviate it.
float wordSize {sizeText(word).x};
if (restrictHeight && currLineLength != 0.0f && maxHeight < lineHeight &&
wordSize > maxLength - textSize.x) {
// Multi-word lines.
if (maxLength - currLineLength + dotsSize < wordSize &&
sizeText(line).x + dotsSize > maxLength) {
while (sizeText(line).x + dotsSize > maxLength)
line.pop_back();
}
else {
while (word != "" && wordSize + dotsSize > maxLength - currLineLength) {
word.pop_back();
wordSize = sizeText(word).x;
}
line = line + word;
}
if (line.back() == ' ')
line.pop_back();
line.append("...");
break;
}
if (wordSize > maxLength) {
if (line != "" && line.back() != '\n') {
if (restrictHeight) {
if (accumHeight + lineHeight > maxHeight)
continue;
accumHeight += lineHeight;
}
line.append("\n");
}
const float cutTarget {wordSize - maxLength + dotsSize};
float cutSize {0.0f};
while (word != "" && cutSize < cutTarget) {
cutSize += sizeText(word.substr(word.size() - 1)).x;
word.pop_back();
}
word.append("...");
line = line + word;
continue;
}
else {
out += line + '\n';
if (restrictHeight)
accumHeight += lineHeight;
if (restrictHeight && accumHeight > maxHeight) {
out.pop_back();
skipLastLine = true;
break;
}
}
line = word;
}
}
// Whatever's left should fit.
if (!skipLastLine)
out.append(line);
if (restrictHeight && out.back() == '\n')
out.pop_back();
// If the text has been abbreviated vertically then add "..." at the end of the string.
if (restrictHeight && accumHeight > maxHeight) {
if (out.back() != '\n') {
float cutSize {0.0f};
float cutTarget {sizeText(line).x - maxLength + dotsSize};
while (cutSize < cutTarget) {
cutSize += sizeText(out.substr(out.size() - 1)).x;
out.pop_back();
}
}
if (out.back() == ' ')
out.pop_back();
out.append("...");
}
return out;
}
glm::vec2 Font::sizeWrappedText(std::string text, float xLen, float lineSpacing)
{
text = wrapText(text, xLen);
return sizeText(text, lineSpacing);
}
glm::vec2 Font::getWrappedTextCursorOffset(std::string text,
float xLen,
size_t stop,
float lineSpacing)
{
std::string wrappedText {wrapText(text, xLen)};
float lineWidth {0.0f}; float lineWidth {0.0f};
float y {0.0f}; float charWidth {0.0f};
float lastSpacePos {0.0f};
size_t wrapCursor {0}; unsigned int charID {0};
size_t cursor {0}; size_t cursor {0};
while (cursor < stop) { size_t lastSpace {0};
unsigned int wrappedCharacter {Utils::String::chars2Unicode(wrappedText, wrapCursor)}; size_t spaceAccum {0};
unsigned int character {Utils::String::chars2Unicode(text, cursor)}; size_t byteCount {0};
std::string wrappedText;
std::string charEntry;
std::vector<std::pair<int, float>> dotsSection;
bool addDots {false};
if (wrappedCharacter == '\n' && character != '\n') { for (size_t i = 0; i < text.length(); ++i) {
// This is where the wordwrap inserted a newline if (text[i] == '\n') {
// Reset lineWidth and increment y, but don't consume .a cursor character. wrappedText.append("\n");
accumHeight += lineHeight;
lineWidth = 0.0f; lineWidth = 0.0f;
y += getHeight(lineSpacing); lastSpace = 0;
cursor = Utils::String::prevCursor(text, cursor); // Unconsume.
continue; continue;
} }
charWidth = 0.0f;
byteCount = 0;
cursor = i;
// Needed to handle multi-byte Unicode characters.
charID = Utils::String::chars2Unicode(text, cursor);
charEntry = text.substr(i, cursor - i);
Glyph* glyph {getGlyph(charID)};
if (glyph != nullptr) {
charWidth = glyph->advance.x;
byteCount = cursor - i;
}
else {
// Missing glyph.
continue;
}
if (multiLine && (charEntry == " " || charEntry == "\t")) {
lastSpace = i;
lastSpacePos = lineWidth;
}
if (lineWidth + charWidth <= maxLength) {
if (lineWidth + charWidth + dotsWidth > maxLength)
dotsSection.emplace_back(std::make_pair(byteCount, charWidth));
lineWidth += charWidth;
wrappedText.append(charEntry);
}
else if (!multiLine) {
addDots = true;
break;
}
else {
if (maxHeight == 0.0f || accumHeight < maxHeight) {
// New row.
float spaceOffset {0.0f};
if (lastSpace == wrappedText.size()) {
wrappedText.append("\n");
}
else if (lastSpace != 0) {
if (lastSpace + spaceAccum == wrappedText.size())
wrappedText.append("\n");
else
wrappedText[lastSpace + spaceAccum] = '\n';
spaceOffset = lineWidth - lastSpacePos;
}
else {
if (lastSpace == 0)
++spaceAccum;
wrappedText.append("\n");
}
if (charEntry != " " && charEntry != "\t") {
wrappedText.append(charEntry);
lineWidth = charWidth;
}
else {
lineWidth = 0.0f;
}
accumHeight += lineHeight;
lineWidth += spaceOffset;
lastSpacePos = 0.0f;
lastSpace = 0;
}
else {
if (multiLine)
addDots = true;
break;
}
}
i = cursor - 1;
}
if (addDots) {
if (wrappedText.back() == ' ') {
lineWidth -= sizeText(" ").x;
wrappedText.pop_back();
}
else if (wrappedText.back() == '\t') {
lineWidth -= sizeText("\t").x;
wrappedText.pop_back();
}
while (!dotsSection.empty() && lineWidth + dotsWidth > maxLength) {
lineWidth -= dotsSection.back().second;
wrappedText.erase(wrappedText.length() - dotsSection.back().first);
dotsSection.pop_back();
}
if (!wrappedText.empty() && wrappedText.back() == ' ')
wrappedText.pop_back();
wrappedText.append("...");
}
mTextSize = {maxLength, accumHeight};
return wrappedText;
}
glm::vec2 Font::getWrappedTextCursorOffset(const std::string& wrappedText,
const size_t stop,
const float lineSpacing)
{
float lineWidth {0.0f};
float yPos {0.0f};
size_t cursor {0};
while (cursor < stop) {
unsigned int character {Utils::String::chars2Unicode(wrappedText, cursor)};
if (character == '\n') { if (character == '\n') {
lineWidth = 0.0f; lineWidth = 0.0f;
y += getHeight(lineSpacing); yPos += getHeight(lineSpacing);
continue; continue;
} }
Glyph* glyph = getGlyph(character); Glyph* glyph {getGlyph(character)};
if (glyph) if (glyph)
lineWidth += glyph->advance.x; lineWidth += glyph->advance.x;
} }
return glm::vec2 {lineWidth, y}; return glm::vec2 {lineWidth, yPos};
} }
float Font::getHeight(float lineSpacing) const float Font::getHeight(float lineSpacing) const
@ -491,7 +463,7 @@ std::shared_ptr<Font> Font::getFromTheme(const ThemeData::ThemeElement* elem,
return orig; return orig;
std::shared_ptr<Font> font; std::shared_ptr<Font> font;
int size {static_cast<int>(orig ? orig->mSize : FONT_SIZE_MEDIUM)}; int size {static_cast<int>(orig ? orig->mFontSize : FONT_SIZE_MEDIUM)};
std::string path {orig ? orig->mPath : getDefaultPath()}; std::string path {orig ? orig->mPath : getDefaultPath()};
float sh {static_cast<float>(Renderer::getScreenHeight())}; float sh {static_cast<float>(Renderer::getScreenHeight())};
@ -546,7 +518,7 @@ size_t Font::getTotalMemUsage()
return total; return total;
} }
Font::FontTexture::FontTexture(const int mSize) Font::FontTexture::FontTexture(const int mFontSize)
{ {
textureId = 0; textureId = 0;
@ -562,10 +534,11 @@ Font::FontTexture::FontTexture(const int mSize)
if (screenSizeModifier < 0.45f) if (screenSizeModifier < 0.45f)
extraTextureSize += 4; extraTextureSize += 4;
// It's not entirely clear if the 20 and 16 constants are correct, but they seem to provide // It's not entirely clear if the 20 and 22 constants are correct, but they seem to provide
// a texture buffer large enough to hold the fonts. This logic is obviously a hack though // a texture buffer large enough to hold the fonts. This logic is obviously a hack though
// and needs to be properly reviewed and improved. // and needs to be properly reviewed and improved.
textureSize = glm::ivec2 {mSize * (20 + extraTextureSize), mSize * (16 + extraTextureSize / 2)}; textureSize =
glm::ivec2 {mFontSize * (20 + extraTextureSize), mFontSize * (22 + extraTextureSize / 2)};
// Make sure the size is not unreasonably large (which may be caused by a mistake in the // Make sure the size is not unreasonably large (which may be caused by a mistake in the
// theme configuration). // theme configuration).
@ -704,7 +677,7 @@ void Font::getTextureForNewGlyph(const glm::ivec2& glyphSize,
return; return;
} }
mTextures.push_back(FontTexture(mSize)); mTextures.push_back(FontTexture(mFontSize));
tex_out = &mTextures.back(); tex_out = &mTextures.back();
tex_out->initTexture(); tex_out->initTexture();
@ -731,7 +704,7 @@ FT_Face Font::getFaceForChar(unsigned int id)
// Otherwise, take from fallbackFonts. // Otherwise, take from fallbackFonts.
const std::string& path {i == 0 ? mPath : fallbackFonts.at(i - 1)}; const std::string& path {i == 0 ? mPath : fallbackFonts.at(i - 1)};
ResourceData data {ResourceManager::getInstance().getFileData(path)}; ResourceData data {ResourceManager::getInstance().getFileData(path)};
mFaceCache[i] = std::unique_ptr<FontFace>(new FontFace(std::move(data), mSize)); mFaceCache[i] = std::unique_ptr<FontFace>(new FontFace(std::move(data), mFontSize));
fit = mFaceCache.find(i); fit = mFaceCache.find(i);
} }
@ -762,7 +735,7 @@ Font::Glyph* Font::getGlyph(const unsigned int id)
if (FT_Load_Char(face, id, FT_LOAD_RENDER)) { if (FT_Load_Char(face, id, FT_LOAD_RENDER)) {
LOG(LogError) << "Couldn't find glyph for character " << id << " for font " << mPath LOG(LogError) << "Couldn't find glyph for character " << id << " for font " << mPath
<< ", size " << mSize; << ", size " << mFontSize;
return nullptr; return nullptr;
} }
@ -776,7 +749,7 @@ Font::Glyph* Font::getGlyph(const unsigned int id)
// size (absurdly large font size). // size (absurdly large font size).
if (tex == nullptr) { if (tex == nullptr) {
LOG(LogError) << "Couldn't create glyph for character " << id << " for font " << mPath LOG(LogError) << "Couldn't create glyph for character " << id << " for font " << mPath
<< ", size " << mSize << " (no suitable texture found)"; << ", size " << mFontSize << " (no suitable texture found)";
return nullptr; return nullptr;
} }

View file

@ -52,6 +52,9 @@ public:
// Returns the portion of a string that fits within the passed argument maxWidth. // Returns the portion of a string that fits within the passed argument maxWidth.
std::string getTextMaxWidth(std::string text, float maxWidth); std::string getTextMaxWidth(std::string text, float maxWidth);
// Returns the size of the overall text area.
const glm::vec2 getTextSize() { return mTextSize; }
TextCache* buildTextCache(const std::string& text, TextCache* buildTextCache(const std::string& text,
float offsetX, float offsetX,
float offsetY, float offsetY,
@ -69,20 +72,17 @@ public:
void renderTextCache(TextCache* cache); void renderTextCache(TextCache* cache);
// Inserts newlines into text to make it wrap properly. // Inserts newlines to make text wrap properly and also abbreviates single-line text.
std::string wrapText(std::string text, std::string wrapText(const std::string& text,
float maxLength, const float maxLength,
float maxHeight = 0.0f, const float maxHeight = 0.0f,
float lineSpacing = 1.5f); const float lineSpacing = 1.5f,
const bool multiLine = false);
// Returns the expected size of a string after wrapping is applied. // Returns the position of the cursor after moving it to the stop position.
glm::vec2 sizeWrappedText(std::string text, float xLen, float lineSpacing = 1.5f); glm::vec2 getWrappedTextCursorOffset(const std::string& wrappedText,
const size_t stop,
// Returns the position of the cursor after moving a "cursor" amount of characters. const float lineSpacing = 1.5f);
glm::vec2 getWrappedTextCursorOffset(std::string text,
float xLen,
size_t cursor,
float lineSpacing = 1.5f);
float getHeight(float lineSpacing = 1.5f) const; float getHeight(float lineSpacing = 1.5f) const;
float getLetterHeight(); float getLetterHeight();
@ -90,7 +90,7 @@ public:
void reload(ResourceManager& rm) override { rebuildTextures(); } void reload(ResourceManager& rm) override { rebuildTextures(); }
void unload(ResourceManager& rm) override { unloadTextures(); } void unload(ResourceManager& rm) override { unloadTextures(); }
int getSize() const { return mSize; } int getSize() const { return mFontSize; }
const std::string& getPath() const { return mPath; } const std::string& getPath() const { return mPath; }
static std::string getDefaultPath() { return FONT_PATH_REGULAR; } static std::string getDefaultPath() { return FONT_PATH_REGULAR; }
@ -117,7 +117,7 @@ private:
glm::ivec2 writePos; glm::ivec2 writePos;
int rowHeight; int rowHeight;
FontTexture(const int mSize); FontTexture(const int mFontSize);
~FontTexture(); ~FontTexture();
bool findEmpty(const glm::ivec2& size, glm::ivec2& cursor_out); bool findEmpty(const glm::ivec2& size, glm::ivec2& cursor_out);
@ -165,8 +165,9 @@ private:
std::map<unsigned int, Glyph> mGlyphMap; std::map<unsigned int, Glyph> mGlyphMap;
Glyph* getGlyph(const unsigned int id); Glyph* getGlyph(const unsigned int id);
int mSize; int mFontSize;
int mMaxGlyphHeight; int mMaxGlyphHeight;
glm::vec2 mTextSize;
const std::string mPath; const std::string mPath;
float getNewlineStartOffset(const std::string& text, float getNewlineStartOffset(const std::string& text,