mirror of
git://projects.qi-hardware.com/gmenu2x.git
synced 2024-11-22 13:05:20 +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 Font::getTextHeight(const string &text)
|
||||||
{
|
{
|
||||||
int nLines = 1;
|
int nLines = 1;
|
||||||
|
@ -22,6 +22,8 @@ public:
|
|||||||
Font(const std::string &path, unsigned int size);
|
Font(const std::string &path, unsigned int size);
|
||||||
~Font();
|
~Font();
|
||||||
|
|
||||||
|
std::string wordWrap(const std::string &text, int width);
|
||||||
|
|
||||||
int getTextWidth(const std::string& text);
|
int getTextWidth(const std::string& text);
|
||||||
int getTextHeight(const std::string& text);
|
int getTextHeight(const std::string& text);
|
||||||
|
|
||||||
@ -37,6 +39,9 @@ public:
|
|||||||
private:
|
private:
|
||||||
Font(TTF_Font *font);
|
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,
|
void writeLine(Surface *surface, std::string const& text,
|
||||||
int x, int y, HAlign halign, VAlign valign);
|
int x, int y, HAlign halign, VAlign valign);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user