1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 15:48:12 +00:00

LibGUI: Wrap words at word break boundaries and don't break up emoji

We will currently only wrap "words" at ASCII spaces and, when wrapping,
will break up multi-code point emoji. This changes word wrapping to
behave as follows:

When the wrapping mode is "anywhere", use the iterator-based font width
computation overload. This will compute the width of multi-code point
emoji, whereas we currently only handle single-code point.

When the wrapping mode is "word", use the Unicode word segmentation
boundaries to break lines.
This commit is contained in:
Timothy Flynn 2023-02-22 07:49:16 -05:00 committed by Sam Atkins
parent 2bc7c11e8d
commit fdf090a299

View file

@ -1878,42 +1878,60 @@ size_t TextEditor::visual_line_containing(size_t line_index, size_t column) cons
void TextEditor::recompute_visual_lines(size_t line_index)
{
auto& line = document().line(line_index);
auto& visual_data = m_line_visual_data[line_index];
auto const& line = document().line(line_index);
size_t line_width_so_far = 0;
auto& visual_data = m_line_visual_data[line_index];
visual_data.visual_line_breaks.clear_with_capacity();
int available_width = visible_text_rect_in_inner_coordinates().width();
auto available_width = visible_text_rect_in_inner_coordinates().width();
auto glyph_spacing = font().glyph_spacing();
if (is_wrapping_enabled()) {
int line_width_so_far = 0;
auto wrap_visual_lines_anywhere = [&]() {
for (auto it = line.view().begin(); it != line.view().end(); ++it) {
auto it_before_computing_glyph_width = it;
auto glyph_width = font().glyph_or_emoji_width(it);
size_t last_whitespace_index = 0;
size_t line_width_since_last_whitespace = 0;
auto glyph_spacing = font().glyph_spacing();
for (size_t i = 0; i < line.length(); ++i) {
auto code_point = line.code_points()[i];
if (is_ascii_space(code_point)) {
last_whitespace_index = i;
line_width_since_last_whitespace = 0;
}
auto glyph_width = font().glyph_or_emoji_width(code_point);
line_width_since_last_whitespace += glyph_width + glyph_spacing;
if ((line_width_so_far + glyph_width + glyph_spacing) > available_width) {
if (m_wrapping_mode == WrappingMode::WrapAtWords && last_whitespace_index != 0) {
// Plus 1 to get the first letter of the word.
visual_data.visual_line_breaks.append(last_whitespace_index + 1);
line_width_so_far = line_width_since_last_whitespace;
last_whitespace_index = 0;
line_width_since_last_whitespace = 0;
} else {
visual_data.visual_line_breaks.append(i);
line_width_so_far = glyph_width + glyph_spacing;
}
continue;
if (line_width_so_far + glyph_width + glyph_spacing > available_width) {
visual_data.visual_line_breaks.append(line.view().iterator_offset(it_before_computing_glyph_width));
line_width_so_far = 0;
}
line_width_so_far += glyph_width + glyph_spacing;
}
};
auto wrap_visual_lines_at_words = [&]() {
size_t last_boundary = 0;
Unicode::for_each_word_segmentation_boundary(line.view(), [&](auto boundary) {
if (boundary == 0)
return IterationDecision::Continue;
auto word = line.view().substring_view(last_boundary, boundary - last_boundary);
auto word_width = font().width(word);
if (line_width_so_far + word_width + glyph_spacing > available_width) {
visual_data.visual_line_breaks.append(last_boundary);
line_width_so_far = 0;
}
line_width_so_far += word_width + glyph_spacing;
last_boundary = boundary;
return IterationDecision::Continue;
});
};
switch (wrapping_mode()) {
case WrappingMode::NoWrap:
break;
case WrappingMode::WrapAnywhere:
wrap_visual_lines_anywhere();
break;
case WrappingMode::WrapAtWords:
wrap_visual_lines_at_words();
break;
}
visual_data.visual_line_breaks.append(line.length());