diff --git a/src/textdialog.cpp b/src/textdialog.cpp index ed60fa0..35ab96a 100644 --- a/src/textdialog.cpp +++ b/src/textdialog.cpp @@ -37,42 +37,79 @@ TextDialog::TextDialog(GMenu2X *gmenu2x, const string &title, const string &desc void TextDialog::preProcess() { unsigned i = 0; - string row; - while (isize()) { - //clean this row - row = trim(text->at(i)); + while (i < text->size()) { + /* Clean the end of the string, allowing lines that are indented at + * the start to stay as such. */ + string line = rtrim(text->at(i)); - //check if this row is not too long - if (gmenu2x->font->getTextWidth(row)>(int)gmenu2x->resX-15) { - vector words; - split(words, row, " "); + if (gmenu2x->font->getTextWidth(line) > (int) gmenu2x->resX - 15) { + /* At least one full character must fit, in order to advance. */ + size_t fits = 1; + while (fits < line.length() && !isUTF8Starter(line[fits])) { + fits++; + } + size_t doesntFit = fits; - unsigned numWords = words.size(); - //find the maximum number of rows that can be printed on screen - while (gmenu2x->font->getTextWidth(row)>(int)gmenu2x->resX-15 && numWords>0) { - numWords--; - row = ""; - for (unsigned x=0; xfont->getTextWidth(line.substr(0, doesntFit)) <= (int) gmenu2x->resX - 15); + + /* End this loop when N characters fit but N + 1 don't. */ + while (fits + 1 < doesntFit) { + size_t guess = fits + (doesntFit - fits) / 2; + if (!isUTF8Starter(line[guess])) + { + size_t oldGuess = guess; + /* Adjust the guess to the nearest UTF-8 starter that is + * not 'fits' or 'doesntFit'. */ + for (size_t offset = 1; offset < (doesntFit - fits) / 2 - 1; offset++) { + if (isUTF8Starter(line[guess - offset])) { + guess -= offset; + break; + } else if (isUTF8Starter(line[guess + offset])) { + guess += offset; + break; + } + } + /* If there's no such character, exit early. */ + if (guess == oldGuess) { + break; + } + } + if (gmenu2x->font->getTextWidth(line.substr(0, guess)) <= (int) gmenu2x->resX - 15) { + fits = guess; + } else { + doesntFit = guess; + } } - //if numWords==0 then the string must be printed as-is, it cannot be split - if (numWords>0) { - //replace with the shorter version - text->at(i) = row; - - //build the remaining text in another row - row = ""; - for (unsigned x=numWords; xinsert(text->begin()+i+1, row); + /* The line shall be split at the last space-separated word that + * fully fits, or otherwise at the last character that fits. */ + size_t lastSpace = line.find_last_of(" \t\r", fits); + if (lastSpace != string::npos) { + fits = lastSpace; } + + /* Insert the rest in a new slot after this line. + * TODO (Nebuleon) Don't use a vector for this, because all later + * elements are moved, which is inefficient. */ + text->insert(text->begin() + i + 1, ltrim(line.substr(fits))); + line = rtrim(line.substr(0, fits)); } + + /* Put the trimmed whole line or the smaller split of the split line + * back into the same slot */ + text->at(i) = line; + i++; } } diff --git a/src/utilities.cpp b/src/utilities.cpp index 23ca7cf..c85fff0 100644 --- a/src/utilities.cpp +++ b/src/utilities.cpp @@ -46,6 +46,16 @@ string trim(const string& s) { return b == string::npos ? "" : string(s, b, e + 1 - b); } +string ltrim(const string& s) { + auto b = s.find_first_not_of(" \t\r"); + return b == string::npos ? "" : string(s, b); +} + +string rtrim(const string& s) { + auto e = s.find_last_not_of(" \t\r"); + return e == string::npos ? "" : string(s, 0, e + 1); +} + bool fileExists(const string &file) { fstream fin; fin.open(file.c_str() ,ios::in); diff --git a/src/utilities.h b/src/utilities.h index 11ea6eb..aa29308 100644 --- a/src/utilities.h +++ b/src/utilities.h @@ -35,8 +35,16 @@ public: bool operator()(const std::string &left, const std::string &right) const; }; +inline bool isUTF8Starter(char c) { + return (c & 0xC0) != 0x80; +} + /** Returns the string with whitespace stripped from both ends. */ std::string trim(const std::string& s); +/** Returns the string with whitespace stripped from the start. */ +std::string ltrim(const std::string& s); +/** Returns the string with whitespace stripped from the end. */ +std::string rtrim(const std::string& s); std::string strreplace(std::string orig, const std::string &search, const std::string &replace); std::string cmdclean(std::string cmdline);