Implement a better text wrapping algorithm

This commit is contained in:
Daniel Schulte 2020-11-27 00:32:10 +01:00
parent eead85d9be
commit b92763b216
2 changed files with 44 additions and 12 deletions

55
tui.cpp
View File

@ -553,20 +553,51 @@ void write_multiline_string(const int x, const int y, const std::string &str, te
}
}
static std::string simple_wrap(const std::string &text, const size_t desired_width)
std::string text_wrap(const std::string &text, const size_t desired_width)
{
std::string out;
size_t current_line_width = 0;
for(const std::string &word: split(text, ' ')) {
size_t w = string_width(word);
if(current_line_width + w < desired_width) {
out.append(word).append(" ");
termpaint_text_measurement *m = termpaint_text_measurement_new(surface);
size_t cur = 0;
size_t next = 0;
do {
next = text.find('\n', cur);
const std::string part = text.substr(cur, next - cur);
termpaint_text_measurement_reset(m);
termpaint_text_measurement_set_limit_width(m, desired_width);
if(termpaint_text_measurement_feed_utf8(m, part.data(), part.size(), true)) {
// Line doesn't fit as a whole
size_t partpos = 0;
do {
size_t fitting_bytes = termpaint_text_measurement_last_ref(m);
bool adjust = part[partpos + fitting_bytes - 1] != ' ';
if(adjust && partpos + fitting_bytes < part.size()) { // If we're inside the part only adjust if the last fitting char isn't the word boundary
adjust = part[partpos + fitting_bytes] != ' ' && part[partpos + fitting_bytes] != '\n';
} else { // Don't adjust at the end of the part
adjust = false;
}
while(adjust && part[partpos + fitting_bytes - 1] != ' ' && fitting_bytes > 0) { // If necessary scan backwards for a space.
fitting_bytes--;
}
if(fitting_bytes == 0) { // Can't be soft broken.
fitting_bytes = termpaint_text_measurement_last_ref(m); // Just get the last fitting character and hard break
adjust = false;
}
const std::string fragment = part.substr(partpos, fitting_bytes - adjust);
out.append(fragment).append("\n");
partpos += fitting_bytes;
termpaint_text_measurement_reset(m);
termpaint_text_measurement_set_limit_width(m, desired_width);
termpaint_text_measurement_feed_utf8(m, part.data() + partpos, part.size() - partpos + 1, partpos == part.size() - 1);
} while(partpos != part.size());
} else {
current_line_width = 0;
out.append("\n").append(word).append(" ");
// Line fits as-is.
out.append(part).append("\n");
}
current_line_width += w + 1;
}
cur = next + 1;
} while(next != std::string::npos);
termpaint_text_measurement_free(m);
if(out.back() == '\n' && text.back() != '\n')
out.pop_back();
return out;
}
@ -574,7 +605,7 @@ void tui_abort(std::string message)
{
const size_t cols = termpaint_surface_width(surface);
message_box("Error", simple_wrap(message, cols/2));
message_box("Error", text_wrap(message, cols/2));
exit(1);
}
@ -703,6 +734,6 @@ void tui_abort(const char *fmt, ...)
const std::string message(buffer.data());
const size_t cols = termpaint_surface_width(surface);
message_box("Error", simple_wrap(message, cols/2));
message_box("Error", text_wrap(message, cols*0.75f));
exit(1);
}

1
tui.h
View File

@ -73,6 +73,7 @@ bool tui_handle_action(const Event &event, const std::vector<action> &actions);
size_t string_width(const std::string &str);
std::pair<size_t, size_t> string_size(const std::string &str);
void write_multiline_string(const int x, const int y, const std::string &str, termpaint_attr *attr);
std::string text_wrap(const std::string &text, const size_t desired_width);
Button message_box(const std::string &caption, const std::string &text, const Button buttons=Button::Ok, const Button default_button=Button::Ok, const Align align=Align::Center);
int get_selection(const std::string &caption, const std::vector<std::string> &choices, size_t selected=0, const Align align=Align::Center);