Bladeren bron

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.
Timothy Flynn 2 jaren geleden
bovenliggende
commit
fdf090a299
1 gewijzigde bestanden met toevoegingen van 47 en 29 verwijderingen
  1. 47 29
      Userland/Libraries/LibGUI/TextEditor.cpp

+ 47 - 29
Userland/Libraries/LibGUI/TextEditor.cpp

@@ -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;
-
-        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;
+    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);
+
+            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());