From 508ea879632baa63d4871f88440d7900166c14ff Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Sat, 8 Oct 2022 09:33:57 +0200 Subject: [PATCH] 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. --- es-app/src/guis/GuiMenu.cpp | 2 +- es-core/src/components/OptionListComponent.h | 22 +- es-core/src/components/TextComponent.cpp | 122 ++------ es-core/src/components/TextComponent.h | 12 +- es-core/src/components/TextEditComponent.cpp | 45 ++- es-core/src/components/TextEditComponent.h | 2 + es-core/src/resources/Font.cpp | 297 +++++++++---------- es-core/src/resources/Font.h | 33 ++- 8 files changed, 214 insertions(+), 321 deletions(-) diff --git a/es-app/src/guis/GuiMenu.cpp b/es-app/src/guis/GuiMenu.cpp index e9c9af7bb..b57c21259 100644 --- a/es-app/src/guis/GuiMenu.cpp +++ b/es-app/src/guis/GuiMenu.cpp @@ -296,7 +296,7 @@ void GuiMenu::openUIOptions() it != SystemData::sSystemVector.cend(); ++it) { if ((*it)->getName() != "retropie") { // 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(), Settings::getInstance()->getString("StartupSystem") == (*it)->getName(), diff --git a/es-core/src/components/OptionListComponent.h b/es-core/src/components/OptionListComponent.h index 1224078c6..615e3a35a 100644 --- a/es-core/src/components/OptionListComponent.h +++ b/es-core/src/components/OptionListComponent.h @@ -342,26 +342,8 @@ private: // A maximum length parameter has been passed and the "name" size surpasses // this value, so abbreviate the string inside the arrows. auto font = Font::get(FONT_SIZE_MEDIUM); - // Calculate with an extra dot to give some leeway. - float dotsSize {font->sizeText("....").x}; - 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)); + mText.setText(Utils::String::toUpper( + font->wrapText(Utils::String::toUpper(it->name), it->maxNameLength))); } else { mText.setText(Utils::String::toUpper(it->name)); diff --git a/es-core/src/components/TextComponent.cpp b/es-core/src/components/TextComponent.cpp index f813dfd33..15c7130a5 100644 --- a/es-core/src/components/TextComponent.cpp +++ b/es-core/src/components/TextComponent.cpp @@ -236,110 +236,52 @@ 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() { if (!mVerticalAutoSizing) 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; - if (mUppercase) - text = Utils::String::toUpper(mText); - else if (mLowercase) - text = Utils::String::toLower(mText); - else if (mCapitalize) - text = Utils::String::toCapitalized(mText); - else - text = mText; // Original case. + if (mText != "") { + if (mUppercase) + text = Utils::String::toUpper(mText); + else if (mLowercase) + text = Utils::String::toLower(mText); + else if (mCapitalize) + text = Utils::String::toCapitalized(mText); + else + text = mText; // Original case. + } - std::shared_ptr f {mFont}; - const float lineHeight {f->getHeight(mLineSpacing)}; - const bool isMultiline {mSize.y > lineHeight}; + if (mFont && mAutoCalcExtent.x) + mSize = mFont->sizeText(text, mLineSpacing); + + if (!mFont || text.empty() || mSize.x < 0.0f) + return; + + std::shared_ptr font {mFont}; + const float lineHeight {font->getHeight(mLineSpacing)}; + const bool isMultiline {mAutoCalcExtent.y == 1 || mSize.y > lineHeight}; const bool isScrollable {mParent && mParent->isScrollable()}; - bool addAbbrev {false}; - if (!isMultiline) { - size_t newline {text.find('\n')}; - // Single line of text - stop at the first newline since it'll mess everything up. - 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(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(f->buildTextCache(wrappedText, glm::vec2 {}, mColor, - mSize.x, mHorizontalAlignment, - mLineSpacing, mNoTopMargin)); + if (isMultiline && text.size() && !isScrollable) { + const std::string wrappedText { + font->wrapText(text, mSize.x, (mVerticalAutoSizing ? 0.0f : mSize.y - lineHeight), + mLineSpacing, isMultiline)}; + mTextCache = std::shared_ptr( + font->buildTextCache(wrappedText, glm::vec2 {0.0f, 0.0f}, mColor, mSize.x, + mHorizontalAlignment, mLineSpacing, mNoTopMargin)); } else { - mTextCache = std::shared_ptr( - f->buildTextCache(f->wrapText(text, mSize.x), glm::vec2 {0.0f, 0.0f}, mColor, mSize.x, - mHorizontalAlignment, mLineSpacing, mNoTopMargin)); + mTextCache = std::shared_ptr(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) setOpacity(mOpacity); diff --git a/es-core/src/components/TextComponent.h b/es-core/src/components/TextComponent.h index 235da987b..a8a04be9b 100644 --- a/es-core/src/components/TextComponent.h +++ b/es-core/src/components/TextComponent.h @@ -14,13 +14,10 @@ class ThemeData; -// Used to display text. -// TextComponent::setSize(x, y) works a little differently than most components: -// * (0, 0) - Will automatically calculate a size that fits -// the text on one line (expand horizontally). -// * (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. +// TextComponent sizing works in the following ways: +// setSize(0.0f, 0.0f) - Automatically sizes single-line text by expanding horizontally. +// setSize(width, 0.0f) - Limits size horizontally and automatically expands vertically. +// setSize(width, height) - Wraps and abbreviates text inside the width and height boundaries. class TextComponent : public GuiComponent { public: @@ -89,7 +86,6 @@ protected: std::shared_ptr mFont; private: - void calculateExtent(); void onColorChanged(); static inline std::vector supportedSystemdataTypes { diff --git a/es-core/src/components/TextEditComponent.cpp b/es-core/src/components/TextEditComponent.cpp index ff5406a82..29de3f5b0 100644 --- a/es-core/src/components/TextEditComponent.cpp +++ b/es-core/src/components/TextEditComponent.cpp @@ -27,6 +27,7 @@ TextEditComponent::TextEditComponent() , mBlinkTime {0} , mCursorRepeatDir {0} , mScrollOffset {0.0f, 0.0f} + , mCursorPos {0.0f, 0.0f} , mBox {":/graphics/textinput.svg"} , mFont {Font::get(FONT_SIZE_MEDIUM, FONT_PATH_LIGHT)} { @@ -51,6 +52,9 @@ void TextEditComponent::onFocusLost() void TextEditComponent::onSizeChanged() { + if (mSize.x == 0.0f || mSize.y == 0.0f) + return; + mBox.fitTo( mSize, glm::vec3 {}, glm::vec2 {-34.0f, -32.0f - (TEXT_PADDING_VERT * Renderer::getScreenHeightModifier())}); @@ -263,9 +267,10 @@ void TextEditComponent::setCursor(size_t pos) 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(mFont->buildTextCache( - wrappedText, 0.0f, 0.0f, 0x77777700 | static_cast(mOpacity * 255.0f))); + mWrappedText, 0.0f, 0.0f, 0x77777700 | static_cast(mOpacity * 255.0f))); if (mCursor > static_cast(mText.length())) mCursor = static_cast(mText.length()); @@ -274,22 +279,23 @@ void TextEditComponent::onTextChanged() void TextEditComponent::onCursorChanged() { if (isMultiline()) { - glm::vec2 textSize {mFont->getWrappedTextCursorOffset(mText, getTextAreaSize().x, mCursor)}; + mCursorPos = mFont->getWrappedTextCursorOffset(mWrappedText, mCursor); // Need to scroll down? - if (mScrollOffset.y + getTextAreaSize().y < textSize.y + mFont->getHeight()) - mScrollOffset.y = textSize.y - getTextAreaSize().y + mFont->getHeight(); + if (mScrollOffset.y + getTextAreaSize().y < mCursorPos.y + mFont->getHeight()) + mScrollOffset.y = mCursorPos.y - getTextAreaSize().y + mFont->getHeight(); // Need to scroll up? - else if (mScrollOffset.y > textSize.y) - mScrollOffset.y = textSize.y; + else if (mScrollOffset.y > mCursorPos.y) + mScrollOffset.y = mCursorPos.y; } 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) - mScrollOffset.x = cursorPos.x - getTextAreaSize().x; - else if (mScrollOffset.x > cursorPos.x) - mScrollOffset.x = cursorPos.x; + if (mScrollOffset.x + getTextAreaSize().x < mCursorPos.x) + mScrollOffset.x = mCursorPos.x - getTextAreaSize().x; + else if (mScrollOffset.x > mCursorPos.x) + mScrollOffset.x = mCursorPos.x; } } @@ -323,25 +329,16 @@ void TextEditComponent::render(const glm::mat4& parentTrans) mRenderer->popClipRect(); // Draw cursor. - glm::vec2 cursorPos; - 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; + float cursorHeight {mFont->getHeight() * 0.8f}; 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, 0xC7C7C7FF); } 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, 0x777777FF); } diff --git a/es-core/src/components/TextEditComponent.h b/es-core/src/components/TextEditComponent.h index 995e44744..36cb9f44e 100644 --- a/es-core/src/components/TextEditComponent.h +++ b/es-core/src/components/TextEditComponent.h @@ -59,6 +59,7 @@ private: Renderer* mRenderer; std::string mText; + std::string mWrappedText; std::string mTextOrig; bool mFocused; bool mEditing; @@ -70,6 +71,7 @@ private: int mCursorRepeatDir; glm::vec2 mScrollOffset; + glm::vec2 mCursorPos; NinePatchComponent mBox; diff --git a/es-core/src/resources/Font.cpp b/es-core/src/resources/Font.cpp index 3a220d0d5..5178c9fd5 100644 --- a/es-core/src/resources/Font.cpp +++ b/es-core/src/resources/Font.cpp @@ -20,16 +20,17 @@ std::map, std::weak_ptr> Font::sFontMap; Font::Font(int size, const std::string& path) : mRenderer {Renderer::getInstance()} - , mSize(size) + , mFontSize(size) , mMaxGlyphHeight {0} + , mTextSize {0.0f, 0.0f} , mPath(path) { - if (mSize < 9) { - mSize = 9; + if (mFontSize < 9) { + mFontSize = 9; LOG(LogWarning) << "Requested font size too small, changing to minimum supported size"; } - else if (mSize > Renderer::getScreenHeight()) { - mSize = static_cast(Renderer::getScreenHeight()); + else if (mFontSize > Renderer::getScreenHeight()) { + mFontSize = static_cast(Renderer::getScreenHeight()); LOG(LogWarning) << "Requested font size too large, changing to maximum supported size"; } @@ -47,7 +48,7 @@ Font::~Font() { unload(ResourceManager::getInstance()); - auto fontEntry = sFontMap.find(std::pair(mPath, mSize)); + auto fontEntry = sFontMap.find(std::pair(mPath, mFontSize)); if (fontEntry != sFontMap.cend()) 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); - - 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}; + assert(maxLength > 0.0f); const float lineHeight {getHeight(lineSpacing)}; - float accumHeight {0.0f}; - const bool restrictHeight {maxHeight > 0.0f}; - bool skipLastLine {false}; - float currLineLength {0.0f}; + const float dotsWidth {sizeText("...").x}; + float accumHeight {lineHeight}; + float lineWidth {0.0f}; + float charWidth {0.0f}; + float lastSpacePos {0.0f}; + unsigned int charID {0}; + size_t cursor {0}; + size_t lastSpace {0}; + size_t spaceAccum {0}; + size_t byteCount {0}; + std::string wrappedText; + std::string charEntry; + std::vector> dotsSection; + bool addDots {false}; - // 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; + for (size_t i = 0; i < text.length(); ++i) { + if (text[i] == '\n') { + wrappedText.append("\n"); + accumHeight += lineHeight; + lineWidth = 0.0f; + lastSpace = 0; 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 { - // 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(); + // 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 { - while (word != "" && wordSize + dotsSize > maxLength - currLineLength) { - word.pop_back(); - wordSize = sizeText(word).x; - } - - line = line + word; + if (lastSpace == 0) + ++spaceAccum; + wrappedText.append("\n"); } - - 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"); + if (charEntry != " " && charEntry != "\t") { + wrappedText.append(charEntry); + lineWidth = charWidth; } - - 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(); + else { + lineWidth = 0.0f; } - - word.append("..."); - line = line + word; - continue; + accumHeight += lineHeight; + lineWidth += spaceOffset; + lastSpacePos = 0.0f; + lastSpace = 0; } 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 (multiLine) + addDots = true; + break; } } - if (out.back() == ' ') - out.pop_back(); - out.append("..."); + + i = cursor - 1; } - return out; + 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::sizeWrappedText(std::string text, float xLen, float lineSpacing) +glm::vec2 Font::getWrappedTextCursorOffset(const std::string& wrappedText, + const size_t stop, + const 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 y {0.0f}; - - size_t wrapCursor {0}; + float yPos {0.0f}; size_t cursor {0}; + while (cursor < stop) { - unsigned int wrappedCharacter {Utils::String::chars2Unicode(wrappedText, wrapCursor)}; - unsigned int character {Utils::String::chars2Unicode(text, cursor)}; - - if (wrappedCharacter == '\n' && character != '\n') { - // This is where the wordwrap inserted a newline - // Reset lineWidth and increment y, but don't consume .a cursor character. - lineWidth = 0.0f; - y += getHeight(lineSpacing); - - cursor = Utils::String::prevCursor(text, cursor); // Unconsume. - continue; - } - + unsigned int character {Utils::String::chars2Unicode(wrappedText, cursor)}; if (character == '\n') { lineWidth = 0.0f; - y += getHeight(lineSpacing); + yPos += getHeight(lineSpacing); continue; } - Glyph* glyph = getGlyph(character); + Glyph* glyph {getGlyph(character)}; if (glyph) lineWidth += glyph->advance.x; } - return glm::vec2 {lineWidth, y}; + return glm::vec2 {lineWidth, yPos}; } float Font::getHeight(float lineSpacing) const @@ -491,7 +463,7 @@ std::shared_ptr Font::getFromTheme(const ThemeData::ThemeElement* elem, return orig; std::shared_ptr font; - int size {static_cast(orig ? orig->mSize : FONT_SIZE_MEDIUM)}; + int size {static_cast(orig ? orig->mFontSize : FONT_SIZE_MEDIUM)}; std::string path {orig ? orig->mPath : getDefaultPath()}; float sh {static_cast(Renderer::getScreenHeight())}; @@ -546,7 +518,7 @@ size_t Font::getTotalMemUsage() return total; } -Font::FontTexture::FontTexture(const int mSize) +Font::FontTexture::FontTexture(const int mFontSize) { textureId = 0; @@ -562,10 +534,11 @@ Font::FontTexture::FontTexture(const int mSize) if (screenSizeModifier < 0.45f) 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 // 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 // theme configuration). @@ -704,7 +677,7 @@ void Font::getTextureForNewGlyph(const glm::ivec2& glyphSize, return; } - mTextures.push_back(FontTexture(mSize)); + mTextures.push_back(FontTexture(mFontSize)); tex_out = &mTextures.back(); tex_out->initTexture(); @@ -731,7 +704,7 @@ FT_Face Font::getFaceForChar(unsigned int id) // Otherwise, take from fallbackFonts. const std::string& path {i == 0 ? mPath : fallbackFonts.at(i - 1)}; ResourceData data {ResourceManager::getInstance().getFileData(path)}; - mFaceCache[i] = std::unique_ptr(new FontFace(std::move(data), mSize)); + mFaceCache[i] = std::unique_ptr(new FontFace(std::move(data), mFontSize)); 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)) { LOG(LogError) << "Couldn't find glyph for character " << id << " for font " << mPath - << ", size " << mSize; + << ", size " << mFontSize; return nullptr; } @@ -776,7 +749,7 @@ Font::Glyph* Font::getGlyph(const unsigned int id) // size (absurdly large font size). if (tex == nullptr) { 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; } diff --git a/es-core/src/resources/Font.h b/es-core/src/resources/Font.h index 941920c96..ef6b11302 100644 --- a/es-core/src/resources/Font.h +++ b/es-core/src/resources/Font.h @@ -52,6 +52,9 @@ public: // Returns the portion of a string that fits within the passed argument 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, float offsetX, float offsetY, @@ -69,20 +72,17 @@ public: void renderTextCache(TextCache* cache); - // Inserts newlines into text to make it wrap properly. - std::string wrapText(std::string text, - float maxLength, - float maxHeight = 0.0f, - float lineSpacing = 1.5f); + // Inserts newlines to make text wrap properly and also abbreviates single-line text. + std::string wrapText(const std::string& text, + const float maxLength, + const float maxHeight = 0.0f, + const float lineSpacing = 1.5f, + const bool multiLine = false); - // Returns the expected size of a string after wrapping is applied. - glm::vec2 sizeWrappedText(std::string text, float xLen, float lineSpacing = 1.5f); - - // Returns the position of the cursor after moving a "cursor" amount of characters. - glm::vec2 getWrappedTextCursorOffset(std::string text, - float xLen, - size_t cursor, - float lineSpacing = 1.5f); + // Returns the position of the cursor after moving it to the stop position. + glm::vec2 getWrappedTextCursorOffset(const std::string& wrappedText, + const size_t stop, + const float lineSpacing = 1.5f); float getHeight(float lineSpacing = 1.5f) const; float getLetterHeight(); @@ -90,7 +90,7 @@ public: void reload(ResourceManager& rm) override { rebuildTextures(); } void unload(ResourceManager& rm) override { unloadTextures(); } - int getSize() const { return mSize; } + int getSize() const { return mFontSize; } const std::string& getPath() const { return mPath; } static std::string getDefaultPath() { return FONT_PATH_REGULAR; } @@ -117,7 +117,7 @@ private: glm::ivec2 writePos; int rowHeight; - FontTexture(const int mSize); + FontTexture(const int mFontSize); ~FontTexture(); bool findEmpty(const glm::ivec2& size, glm::ivec2& cursor_out); @@ -165,8 +165,9 @@ private: std::map mGlyphMap; Glyph* getGlyph(const unsigned int id); - int mSize; + int mFontSize; int mMaxGlyphHeight; + glm::vec2 mTextSize; const std::string mPath; float getNewlineStartOffset(const std::string& text,