mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-10 13:00:29 +03: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:
parent
7207276697
commit
2e11c16be4
Notes:
sideshowbarker
2024-07-19 06:29:56 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/2e11c16be4b
@ -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(' ');
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user