1
0
mirror of git://projects.qi-hardware.com/gmenu2x.git synced 2024-06-30 20:34:11 +03:00

Implement word wrapping as a method of Font

This implementation is based on the implementation in TextDialog::preProcess,
with one major difference: it works on the entire input string, copies it much
less as part of its function, and tries to quickly establish a small search
space for the length of the beginning split of each line.

With most standard fonts and sizes, this means up to 9 computations of metrics
per output line.
This commit is contained in:
Nebuleon Fumika 2014-07-24 01:23:47 +00:00 committed by Maarten ter Huurne
parent b08a285b37
commit 0c860e8b90
2 changed files with 119 additions and 0 deletions

View File

@ -74,6 +74,120 @@ int Font::getTextWidth(const string &text)
}
}
string Font::wordWrap(const string &text, int width)
{
string result;
result.reserve(text.length());
size_t start = 0, end = text.find('\n');
while (end != string::npos) {
if (start != end) {
result.append(wordWrapSingleLine(text, start, end, width));
}
result.append("\n");
start = end + 1;
end = text.find('\n', start);
}
if (start < text.length()) {
result.append(wordWrapSingleLine(text, start, text.length(), width));
}
return result;
}
string Font::wordWrapSingleLine(const string &text, size_t start, size_t end, int width)
{
string result;
result.reserve(end - start);
while (start != end) {
/* Clean the end of the string, allowing lines that are indented at
* the start to stay as such. */
string run = rtrim(text.substr(start, end - start));
int runWidth = getTextWidth(run);
if (runWidth > width) {
size_t fits = 0, doesntFit = run.length();
/* First guess: width / runWidth approximates the proportion of
* the run that should fit. */
size_t guess = min(run.length(), (size_t) (doesntFit * ((float) width / runWidth)));
/* Adjust that to fully include any partial UTF-8 character. */
while (guess < run.length() && !isUTF8Starter(run[guess])) {
guess++;
}
if (getTextWidth(run.substr(0, guess)) <= width) {
fits = guess;
doesntFit = fits;
/* Prime doesntFit, which should be closer to 2 * fits than
* to run.length() / 2 if the run is long. */
do {
fits = doesntFit; // determined to fit by a previous iteration
doesntFit = min(2 * fits, run.length());
while (doesntFit < run.length() && !isUTF8Starter(run[doesntFit])) {
doesntFit++;
}
} while (doesntFit < run.length() && getTextWidth(run.substr(0, doesntFit)) <= width);
} else {
doesntFit = guess;
}
/* End this loop when N full characters fit but N + 1 don't. */
while (fits + 1 < doesntFit) {
size_t guess = fits + (doesntFit - fits) / 2;
if (!isUTF8Starter(run[guess])) {
size_t oldGuess = guess;
/* Adjust the guess to fully include a UTF-8 character. */
for (size_t offset = 1; offset < (doesntFit - fits) / 2 - 1; offset++) {
if (isUTF8Starter(run[guess - offset])) {
guess -= offset;
break;
} else if (isUTF8Starter(run[guess + offset])) {
guess += offset;
break;
}
}
/* If there's no such character, exit early. */
if (guess == oldGuess) {
break;
}
}
if (getTextWidth(run.substr(0, guess)) <= width) {
fits = guess;
} else {
doesntFit = guess;
}
}
/* The run shall be split at the last space-separated word that
* fully fits, or otherwise at the last character that fits. */
size_t lastSpace = run.find_last_of(" \t\r", fits);
if (lastSpace != string::npos) {
fits = lastSpace;
}
/* If 0 characters fit, we'll have to make 1 fit anyway, otherwise
* we're in for an infinite loop. This can happen if the font size
* is large. */
if (fits == 0) {
fits = 1;
while (fits < run.length() && !isUTF8Starter(run[fits])) {
fits++;
}
}
result.append(rtrim(run.substr(0, fits))).append("\n");
start = min(end, text.find_first_not_of(" \t\r", start + fits));
} else {
result.append(rtrim(run));
start = end;
}
}
return result;
}
int Font::getTextHeight(const string &text)
{
int nLines = 1;

View File

@ -22,6 +22,8 @@ public:
Font(const std::string &path, unsigned int size);
~Font();
std::string wordWrap(const std::string &text, int width);
int getTextWidth(const std::string& text);
int getTextHeight(const std::string& text);
@ -37,6 +39,9 @@ public:
private:
Font(TTF_Font *font);
std::string wordWrapSingleLine(const std::string &text,
size_t start, size_t end, int width);
void writeLine(Surface *surface, std::string const& text,
int x, int y, HAlign halign, VAlign valign);