1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 04:08:11 +00:00

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.
This commit is contained in:
Andreas Kling 2020-05-18 16:38:28 +02:00
parent 7207276697
commit 2e11c16be4
2 changed files with 47 additions and 32 deletions

View file

@ -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)); 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) { switch (m_text_alignment) {
case Gfx::TextAlignment::CenterLeft: case Gfx::TextAlignment::CenterLeft:
if (position.x() <= 0) for_each_visual_line(line_index, [&](const Gfx::Rect& rect, auto& view, size_t start_of_line) {
column_index = 0; if (rect.contains_vertically(position.y())) {
else column_index = start_of_line;
column_index = (position.x() + glyph_width() / 2) / glyph_width(); if (position.x() <= 0) {
if (is_line_wrapping_enabled()) { // We're outside the text on the left side, put cursor at column 0 on this visual line.
for_each_visual_line(line_index, [&](const Gfx::Rect& rect, auto&, size_t start_of_line) { } else {
if (rect.contains_vertically(position.y())) { int glyph_x = 0;
column_index += start_of_line; size_t i = 0;
return IterationDecision::Break; 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; break;
case Gfx::TextAlignment::CenterRight: case Gfx::TextAlignment::CenterRight:
// FIXME: Support right-aligned line wrapping, I guess. // FIXME: Support right-aligned line wrapping, I guess.
ASSERT(!is_line_wrapping_enabled()); 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; break;
default: default:
ASSERT_NOT_REACHED(); ASSERT_NOT_REACHED();
@ -440,9 +447,9 @@ void TextEditor::paint_event(PaintEvent& event)
auto color = palette().color(is_enabled() ? foreground_role() : Gfx::ColorRole::DisabledText); auto color = palette().color(is_enabled() ? foreground_role() : Gfx::ColorRole::DisabledText);
painter.draw_text(visual_line_rect, visual_line_text, m_text_alignment, color); painter.draw_text(visual_line_rect, visual_line_text, m_text_alignment, color);
} else { } else {
int advance = font().glyph_width(' ') + font().glyph_spacing(); Gfx::Rect character_rect = { visual_line_rect.location(), { 0, line_height() } };
Gfx::Rect character_rect = { visual_line_rect.location(), { font().glyph_width(' '), line_height() } };
for (size_t i = 0; i < visual_line_text.length(); ++i) { 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(); const Gfx::Font* font = &this->font();
Color color; Color color;
Optional<Color> background_color; Optional<Color> background_color;
@ -459,16 +466,17 @@ void TextEditor::paint_event(PaintEvent& event)
underline = span.is_underlined; underline = span.is_underlined;
break; break;
} }
character_rect.set_width(font->glyph_width(codepoint) + font->glyph_spacing());
if (background_color.has_value()) if (background_color.has_value())
painter.fill_rect(character_rect, background_color.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); painter.draw_text(character_rect, visual_line_text.substring_view(i, 1), *font, m_text_alignment, color);
if (underline) { if (underline) {
painter.draw_line(character_rect.bottom_left().translated(0, 1), character_rect.bottom_right().translated(0, 1), color); 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) { 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 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; 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 int TextEditor::content_x_for_position(const TextPosition& position) const
{ {
auto& line = this->line(position.line()); auto& line = this->line(position.line());
int x_offset = -1; int x_offset = 0;
switch (m_text_alignment) { switch (m_text_alignment) {
case Gfx::TextAlignment::CenterLeft: case Gfx::TextAlignment::CenterLeft:
for_each_visual_line(position.line(), [&](const Gfx::Rect&, auto& view, size_t start_of_visual_line) { for_each_visual_line(position.line(), [&](const Gfx::Rect&, auto& visual_line_view, size_t start_of_visual_line) {
if (position.column() >= start_of_visual_line && ((position.column() - start_of_visual_line) <= view.length())) { size_t offset_in_visual_line = position.column() - start_of_visual_line;
x_offset = (position.column() - start_of_visual_line) * glyph_width(); 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::Break;
} }
return IterationDecision::Continue; return IterationDecision::Continue;
@ -942,7 +956,7 @@ int TextEditor::content_x_for_position(const TextPosition& position) const
case Gfx::TextAlignment::CenterRight: case Gfx::TextAlignment::CenterRight:
// FIXME // FIXME
ASSERT(!is_line_wrapping_enabled()); 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: default:
ASSERT_NOT_REACHED(); ASSERT_NOT_REACHED();
} }
@ -1016,7 +1030,7 @@ Gfx::Rect TextEditor::line_content_rect(size_t line_index) const
{ {
auto& line = this->line(line_index); auto& line = this->line(line_index);
if (is_single_line()) { 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() }); line_rect.center_vertically_within({ {}, frame_inner_rect().size() });
return line_rect; return line_rect;
} }
@ -1025,7 +1039,7 @@ Gfx::Rect TextEditor::line_content_rect(size_t line_index) const
return { return {
content_x_for_position({ line_index, 0 }), content_x_for_position({ line_index, 0 }),
(int)line_index * line_height(), (int)line_index * line_height(),
(int)line.length() * glyph_width(), font().width(line.view()),
line_height() line_height()
}; };
} }
@ -1385,15 +1399,16 @@ void TextEditor::recompute_visual_lines(size_t line_index)
if (is_line_wrapping_enabled()) { if (is_line_wrapping_enabled()) {
int line_width_so_far = 0; int line_width_so_far = 0;
auto glyph_spacing = font().glyph_spacing();
for (size_t i = 0; i < line.length(); ++i) { for (size_t i = 0; i < line.length(); ++i) {
auto codepoint = line.codepoints()[i]; auto codepoint = line.codepoints()[i];
auto glyph_width = font().glyph_or_emoji_width(codepoint); 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); visual_data.visual_line_breaks.append(i);
line_width_so_far = glyph_width; line_width_so_far = glyph_width + glyph_spacing;
continue; 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; 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(' ');
} }
} }

View file

@ -85,8 +85,6 @@ public:
int line_height() const; int line_height() const;
TextPosition cursor() const { return m_cursor; } TextPosition cursor() const { return m_cursor; }
TextRange normalized_selection() const { return m_selection.normalized(); } 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&); void insert_at_cursor_or_replace_selection(const StringView&);
bool write_to_file(const StringView& path); bool write_to_file(const StringView& path);
@ -172,6 +170,7 @@ private:
void paint_ruler(Painter&); void paint_ruler(Painter&);
void update_content_size(); void update_content_size();
void did_change(); void did_change();
int fixed_glyph_width() const;
Gfx::Rect line_content_rect(size_t item_index) const; Gfx::Rect line_content_rect(size_t item_index) const;
Gfx::Rect line_widget_rect(size_t line_index) const; Gfx::Rect line_widget_rect(size_t line_index) const;