Browse Source

LibGUI: Support variable-width fonts in TextEditor

This patch reworks metric and coordinate computation to iterate over
text content instead of making assumptions about fixed glyph widths.
Andreas Kling 5 năm trước cách đây
mục cha
commit
2e11c16be4
2 tập tin đã thay đổi với 47 bổ sung32 xóa
  1. 46 30
      Libraries/LibGUI/TextEditor.cpp
  2. 1 2
      Libraries/LibGUI/TextEditor.h

+ 46 - 30
Libraries/LibGUI/TextEditor.cpp

@@ -159,27 +159,34 @@ TextPosition TextEditor::text_position_at(const Gfx::Point& a_position) const
 
     line_index = max((size_t)0, min(line_index, line_count() - 1));
 
-    size_t column_index;
+    size_t column_index = 0;
     switch (m_text_alignment) {
     case Gfx::TextAlignment::CenterLeft:
-        if (position.x() <= 0)
-            column_index = 0;
-        else
-            column_index = (position.x() + glyph_width() / 2) / glyph_width();
-        if (is_line_wrapping_enabled()) {
-            for_each_visual_line(line_index, [&](const Gfx::Rect& rect, auto&, size_t start_of_line) {
-                if (rect.contains_vertically(position.y())) {
-                    column_index += start_of_line;
-                    return IterationDecision::Break;
+        for_each_visual_line(line_index, [&](const Gfx::Rect& rect, auto& view, size_t start_of_line) {
+            if (rect.contains_vertically(position.y())) {
+                column_index = start_of_line;
+                if (position.x() <= 0) {
+                    // We're outside the text on the left side, put cursor at column 0 on this visual line.
+                } else {
+                    int glyph_x = 0;
+                    size_t i = 0;
+                    for (; i < view.length(); ++i) {
+                        int advance = font().glyph_width(view.codepoints()[i]) + font().glyph_spacing();
+                        if ((glyph_x + (advance / 2)) >= position.x())
+                            break;
+                        glyph_x += advance;
+                    }
+                    column_index += i;
                 }
-                return IterationDecision::Continue;
-            });
-        }
+                return IterationDecision::Break;
+            }
+            return IterationDecision::Continue;
+        });
         break;
     case Gfx::TextAlignment::CenterRight:
         // FIXME: Support right-aligned line wrapping, I guess.
         ASSERT(!is_line_wrapping_enabled());
-        column_index = (position.x() - content_x_for_position({ line_index, 0 }) + glyph_width() / 2) / glyph_width();
+        column_index = (position.x() - content_x_for_position({ line_index, 0 }) + fixed_glyph_width() / 2) / fixed_glyph_width();
         break;
     default:
         ASSERT_NOT_REACHED();
@@ -440,9 +447,9 @@ void TextEditor::paint_event(PaintEvent& event)
                 auto color = palette().color(is_enabled() ? foreground_role() : Gfx::ColorRole::DisabledText);
                 painter.draw_text(visual_line_rect, visual_line_text, m_text_alignment, color);
             } else {
-                int advance = font().glyph_width(' ') + font().glyph_spacing();
-                Gfx::Rect character_rect = { visual_line_rect.location(), { font().glyph_width(' '), line_height() } };
+                Gfx::Rect character_rect = { visual_line_rect.location(), { 0, line_height() } };
                 for (size_t i = 0; i < visual_line_text.length(); ++i) {
+                    u32 codepoint = visual_line_text.substring_view(i, 1).codepoints()[0];
                     const Gfx::Font* font = &this->font();
                     Color color;
                     Optional<Color> background_color;
@@ -459,16 +466,17 @@ void TextEditor::paint_event(PaintEvent& event)
                         underline = span.is_underlined;
                         break;
                     }
+                    character_rect.set_width(font->glyph_width(codepoint) + font->glyph_spacing());
                     if (background_color.has_value())
                         painter.fill_rect(character_rect, background_color.value());
                     painter.draw_text(character_rect, visual_line_text.substring_view(i, 1), *font, m_text_alignment, color);
                     if (underline) {
                         painter.draw_line(character_rect.bottom_left().translated(0, 1), character_rect.bottom_right().translated(0, 1), color);
                     }
-                    character_rect.move_by(advance, 0);
+                    character_rect.move_by(character_rect.width(), 0);
                 }
             }
-            bool physical_line_has_selection = has_selection && line_index >= selection.start().line() && line_index <= selection.end().line();
+
             if (physical_line_has_selection) {
                 size_t start_of_selection_within_visual_line = (size_t)max(0, (int)selection_start_column_within_line - (int)start_of_visual_line);
                 size_t end_of_selection_within_visual_line = selection_end_column_within_line - start_of_visual_line;
@@ -928,12 +936,18 @@ void TextEditor::do_delete()
 int TextEditor::content_x_for_position(const TextPosition& position) const
 {
     auto& line = this->line(position.line());
-    int x_offset = -1;
+    int x_offset = 0;
     switch (m_text_alignment) {
     case Gfx::TextAlignment::CenterLeft:
-        for_each_visual_line(position.line(), [&](const Gfx::Rect&, auto& view, size_t start_of_visual_line) {
-            if (position.column() >= start_of_visual_line && ((position.column() - start_of_visual_line) <= view.length())) {
-                x_offset = (position.column() - start_of_visual_line) * glyph_width();
+        for_each_visual_line(position.line(), [&](const Gfx::Rect&, auto& visual_line_view, size_t start_of_visual_line) {
+            size_t offset_in_visual_line = position.column() - start_of_visual_line;
+            if (position.column() >= start_of_visual_line && (offset_in_visual_line <= visual_line_view.length())) {
+                if (offset_in_visual_line == 0) {
+                    x_offset = 0;
+                } else {
+                    x_offset = font().width(visual_line_view.substring_view(0, offset_in_visual_line));
+                    x_offset += font().glyph_spacing();
+                }
                 return IterationDecision::Break;
             }
             return IterationDecision::Continue;
@@ -942,7 +956,7 @@ int TextEditor::content_x_for_position(const TextPosition& position) const
     case Gfx::TextAlignment::CenterRight:
         // FIXME
         ASSERT(!is_line_wrapping_enabled());
-        return content_width() - m_horizontal_content_padding - (line.length() * glyph_width()) + (position.column() * glyph_width());
+        return content_width() - m_horizontal_content_padding - (line.length() * fixed_glyph_width()) + (position.column() * fixed_glyph_width());
     default:
         ASSERT_NOT_REACHED();
     }
@@ -1016,7 +1030,7 @@ Gfx::Rect TextEditor::line_content_rect(size_t line_index) const
 {
     auto& line = this->line(line_index);
     if (is_single_line()) {
-        Gfx::Rect line_rect = { content_x_for_position({ line_index, 0 }), 0, (int)line.length() * glyph_width(), font().glyph_height() + 2 };
+        Gfx::Rect line_rect = { content_x_for_position({ line_index, 0 }), 0, font().width(line.view()), font().glyph_height() + 2 };
         line_rect.center_vertically_within({ {}, frame_inner_rect().size() });
         return line_rect;
     }
@@ -1025,7 +1039,7 @@ Gfx::Rect TextEditor::line_content_rect(size_t line_index) const
     return {
         content_x_for_position({ line_index, 0 }),
         (int)line_index * line_height(),
-        (int)line.length() * glyph_width(),
+        font().width(line.view()),
         line_height()
     };
 }
@@ -1385,15 +1399,16 @@ void TextEditor::recompute_visual_lines(size_t line_index)
     if (is_line_wrapping_enabled()) {
         int line_width_so_far = 0;
 
+        auto glyph_spacing = font().glyph_spacing();
         for (size_t i = 0; i < line.length(); ++i) {
             auto codepoint = line.codepoints()[i];
             auto glyph_width = font().glyph_or_emoji_width(codepoint);
-            if ((line_width_so_far + glyph_width) > available_width) {
+            if ((line_width_so_far + glyph_width + glyph_spacing) > available_width) {
                 visual_data.visual_line_breaks.append(i);
-                line_width_so_far = glyph_width;
+                line_width_so_far = glyph_width + glyph_spacing;
                 continue;
             }
-            line_width_so_far += glyph_width;
+            line_width_so_far += glyph_width + glyph_spacing;
         }
     }
 
@@ -1558,9 +1573,10 @@ int TextEditor::line_height() const
     return font().glyph_height() + m_line_spacing;
 }
 
-int TextEditor::glyph_width() const
+int TextEditor::fixed_glyph_width() const
 {
-    return font().glyph_width('x');
+    ASSERT(font().is_fixed_width());
+    return font().glyph_width(' ');
 }
 
 }

+ 1 - 2
Libraries/LibGUI/TextEditor.h

@@ -85,8 +85,6 @@ public:
     int line_height() const;
     TextPosition cursor() const { return m_cursor; }
     TextRange normalized_selection() const { return m_selection.normalized(); }
-    // FIXME: This should take glyph spacing into account, no?
-    int glyph_width() const;
 
     void insert_at_cursor_or_replace_selection(const StringView&);
     bool write_to_file(const StringView& path);
@@ -172,6 +170,7 @@ private:
     void paint_ruler(Painter&);
     void update_content_size();
     void did_change();
+    int fixed_glyph_width() const;
 
     Gfx::Rect line_content_rect(size_t item_index) const;
     Gfx::Rect line_widget_rect(size_t line_index) const;