mirror of
git://projects.qi-hardware.com/gmenu2x.git
synced 2024-11-22 13:44:04 +02: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:
parent
b08a285b37
commit
0c860e8b90
114
src/font.cpp
114
src/font.cpp
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user