LibWeb: Support vertical text hit detection and selection

If a PaintableFragment has a vertical orientation, use y instead of x to
compute the offset of each character in the text fragment.
This commit is contained in:
BenJilks 2024-11-02 20:09:07 +00:00 committed by Alexander Kalenik
commit 9395b266c6
Notes: github-actions[bot] 2024-11-03 16:02:50 +00:00
3 changed files with 53 additions and 13 deletions

View file

@ -939,7 +939,7 @@ TraversalDecision PaintableWithLines::hit_test(CSSPixelPoint position, HitTestTy
if (fragment_absolute_rect.contains(position_adjusted_by_scroll_offset)) { if (fragment_absolute_rect.contains(position_adjusted_by_scroll_offset)) {
if (fragment.paintable().hit_test(position, type, callback) == TraversalDecision::Break) if (fragment.paintable().hit_test(position, type, callback) == TraversalDecision::Break)
return TraversalDecision::Break; return TraversalDecision::Break;
HitTestResult hit_test_result { const_cast<Paintable&>(fragment.paintable()), fragment.text_index_at(position_adjusted_by_scroll_offset.x()), 0, 0 }; HitTestResult hit_test_result { const_cast<Paintable&>(fragment.paintable()), fragment.text_index_at(position_adjusted_by_scroll_offset), 0, 0 };
if (callback(hit_test_result) == TraversalDecision::Break) if (callback(hit_test_result) == TraversalDecision::Break)
return TraversalDecision::Break; return TraversalDecision::Break;
} else if (type == HitTestType::TextCursor) { } else if (type == HitTestType::TextCursor) {

View file

@ -36,13 +36,23 @@ CSSPixelRect const PaintableFragment::absolute_rect() const
return rect; return rect;
} }
int PaintableFragment::text_index_at(CSSPixels x) const int PaintableFragment::text_index_at(CSSPixelPoint position) const
{ {
if (!is<TextPaintable>(paintable())) if (!is<TextPaintable>(paintable()))
return 0; return 0;
CSSPixels relative_x = x - absolute_rect().x(); CSSPixels relative_inline_offset = [&]() {
if (relative_x < 0) switch (orientation()) {
case Gfx::Orientation::Horizontal:
return position.x() - absolute_rect().x();
case Gfx::Orientation::Vertical:
return position.y() - absolute_rect().y();
default:
VERIFY_NOT_REACHED();
}
}();
if (relative_inline_offset < 0)
return 0; return 0;
auto const& glyphs = m_glyph_run->glyphs(); auto const& glyphs = m_glyph_run->glyphs();
@ -50,10 +60,10 @@ int PaintableFragment::text_index_at(CSSPixels x) const
auto glyph_position = CSSPixels::nearest_value_for(glyphs[i].position.x()); auto glyph_position = CSSPixels::nearest_value_for(glyphs[i].position.x());
if (i + 1 < glyphs.size()) { if (i + 1 < glyphs.size()) {
auto next_glyph_position = CSSPixels::nearest_value_for(glyphs[i + 1].position.x()); auto next_glyph_position = CSSPixels::nearest_value_for(glyphs[i + 1].position.x());
if (relative_x >= glyph_position && relative_x < next_glyph_position) if (relative_inline_offset >= glyph_position && relative_inline_offset < next_glyph_position)
return m_start + i; return m_start + i;
} else { } else {
if (relative_x >= glyph_position) if (relative_inline_offset >= glyph_position)
return m_start + i; return m_start + i;
} }
} }
@ -90,8 +100,18 @@ CSSPixelRect PaintableFragment::range_rect(Gfx::Font const& font, size_t start_o
auto pixel_width_of_selection = CSSPixels::nearest_value_for(font.width(text.substring_view(selection_start_in_this_fragment, selection_end_in_this_fragment - selection_start_in_this_fragment))) + 1; auto pixel_width_of_selection = CSSPixels::nearest_value_for(font.width(text.substring_view(selection_start_in_this_fragment, selection_end_in_this_fragment - selection_start_in_this_fragment))) + 1;
auto rect = absolute_rect(); auto rect = absolute_rect();
rect.set_x(rect.x() + pixel_distance_to_first_selected_character); switch (orientation()) {
rect.set_width(pixel_width_of_selection); case Gfx::Orientation::Horizontal:
rect.set_x(rect.x() + pixel_distance_to_first_selected_character);
rect.set_width(pixel_width_of_selection);
break;
case Gfx::Orientation::Vertical:
rect.set_y(rect.y() + pixel_distance_to_first_selected_character);
rect.set_height(pixel_width_of_selection);
break;
default:
VERIFY_NOT_REACHED();
}
return rect; return rect;
} }
@ -106,8 +126,18 @@ CSSPixelRect PaintableFragment::range_rect(Gfx::Font const& font, size_t start_o
auto pixel_width_of_selection = CSSPixels::nearest_value_for(font.width(text.substring_view(selection_start_in_this_fragment, selection_end_in_this_fragment - selection_start_in_this_fragment))) + 1; auto pixel_width_of_selection = CSSPixels::nearest_value_for(font.width(text.substring_view(selection_start_in_this_fragment, selection_end_in_this_fragment - selection_start_in_this_fragment))) + 1;
auto rect = absolute_rect(); auto rect = absolute_rect();
rect.set_x(rect.x() + pixel_distance_to_first_selected_character); switch (orientation()) {
rect.set_width(pixel_width_of_selection); case Gfx::Orientation::Horizontal:
rect.set_x(rect.x() + pixel_distance_to_first_selected_character);
rect.set_width(pixel_width_of_selection);
break;
case Gfx::Orientation::Vertical:
rect.set_y(rect.y() + pixel_distance_to_first_selected_character);
rect.set_height(pixel_width_of_selection);
break;
default:
VERIFY_NOT_REACHED();
}
return rect; return rect;
} }
@ -122,8 +152,18 @@ CSSPixelRect PaintableFragment::range_rect(Gfx::Font const& font, size_t start_o
auto pixel_width_of_selection = CSSPixels::nearest_value_for(font.width(text.substring_view(selection_start_in_this_fragment, selection_end_in_this_fragment - selection_start_in_this_fragment))) + 1; auto pixel_width_of_selection = CSSPixels::nearest_value_for(font.width(text.substring_view(selection_start_in_this_fragment, selection_end_in_this_fragment - selection_start_in_this_fragment))) + 1;
auto rect = absolute_rect(); auto rect = absolute_rect();
rect.set_x(rect.x() + pixel_distance_to_first_selected_character); switch (orientation()) {
rect.set_width(pixel_width_of_selection); case Gfx::Orientation::Horizontal:
rect.set_x(rect.x() + pixel_distance_to_first_selected_character);
rect.set_width(pixel_width_of_selection);
break;
case Gfx::Orientation::Vertical:
rect.set_y(rect.y() + pixel_distance_to_first_selected_character);
rect.set_height(pixel_width_of_selection);
break;
default:
VERIFY_NOT_REACHED();
}
return rect; return rect;
} }

View file

@ -52,7 +52,7 @@ public:
CSSPixels width() const { return m_size.width(); } CSSPixels width() const { return m_size.width(); }
CSSPixels height() const { return m_size.height(); } CSSPixels height() const { return m_size.height(); }
int text_index_at(CSSPixels) const; int text_index_at(CSSPixelPoint) const;
StringView string_view() const; StringView string_view() const;