mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2025-01-17 22:55:38 +00:00
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:
parent
13a603a687
commit
508ea87963
|
@ -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(),
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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<Font> 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> 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<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));
|
||||
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<TextCache>(
|
||||
font->buildTextCache(wrappedText, glm::vec2 {0.0f, 0.0f}, mColor, mSize.x,
|
||||
mHorizontalAlignment, mLineSpacing, mNoTopMargin));
|
||||
}
|
||||
else {
|
||||
mTextCache = std::shared_ptr<TextCache>(
|
||||
f->buildTextCache(f->wrapText(text, mSize.x), glm::vec2 {0.0f, 0.0f}, mColor, mSize.x,
|
||||
mHorizontalAlignment, mLineSpacing, mNoTopMargin));
|
||||
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)
|
||||
setOpacity(mOpacity);
|
||||
|
||||
|
|
|
@ -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<Font> mFont;
|
||||
|
||||
private:
|
||||
void calculateExtent();
|
||||
void onColorChanged();
|
||||
|
||||
static inline std::vector<std::string> supportedSystemdataTypes {
|
||||
|
|
|
@ -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<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()))
|
||||
mCursor = static_cast<int>(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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
: 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<int>(Renderer::getScreenHeight());
|
||||
else if (mFontSize > Renderer::getScreenHeight()) {
|
||||
mFontSize = static_cast<int>(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<std::string, int>(mPath, mSize));
|
||||
auto fontEntry = sFontMap.find(std::pair<std::string, int>(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<std::pair<int, float>> 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> Font::getFromTheme(const ThemeData::ThemeElement* elem,
|
|||
return orig;
|
||||
|
||||
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()};
|
||||
|
||||
float sh {static_cast<float>(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<FontFace>(new FontFace(std::move(data), mSize));
|
||||
mFaceCache[i] = std::unique_ptr<FontFace>(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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<unsigned int, Glyph> 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,
|
||||
|
|
Loading…
Reference in a new issue