From 9395b266c6a1db8f1da17d15ea17ffb6b6eea5fa Mon Sep 17 00:00:00 2001 From: BenJilks Date: Sat, 2 Nov 2024 20:09:07 +0000 Subject: [PATCH] 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. --- .../LibWeb/Painting/PaintableBox.cpp | 2 +- .../LibWeb/Painting/PaintableFragment.cpp | 62 +++++++++++++++---- .../LibWeb/Painting/PaintableFragment.h | 2 +- 3 files changed, 53 insertions(+), 13 deletions(-) diff --git a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp index 5e7337fb8a1..19d9340471c 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp +++ b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp @@ -939,7 +939,7 @@ TraversalDecision PaintableWithLines::hit_test(CSSPixelPoint position, HitTestTy if (fragment_absolute_rect.contains(position_adjusted_by_scroll_offset)) { if (fragment.paintable().hit_test(position, type, callback) == TraversalDecision::Break) return TraversalDecision::Break; - HitTestResult hit_test_result { const_cast(fragment.paintable()), fragment.text_index_at(position_adjusted_by_scroll_offset.x()), 0, 0 }; + HitTestResult hit_test_result { const_cast(fragment.paintable()), fragment.text_index_at(position_adjusted_by_scroll_offset), 0, 0 }; if (callback(hit_test_result) == TraversalDecision::Break) return TraversalDecision::Break; } else if (type == HitTestType::TextCursor) { diff --git a/Userland/Libraries/LibWeb/Painting/PaintableFragment.cpp b/Userland/Libraries/LibWeb/Painting/PaintableFragment.cpp index ebb914c9750..e7c31339f9f 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintableFragment.cpp +++ b/Userland/Libraries/LibWeb/Painting/PaintableFragment.cpp @@ -36,13 +36,23 @@ CSSPixelRect const PaintableFragment::absolute_rect() const return rect; } -int PaintableFragment::text_index_at(CSSPixels x) const +int PaintableFragment::text_index_at(CSSPixelPoint position) const { if (!is(paintable())) return 0; - CSSPixels relative_x = x - absolute_rect().x(); - if (relative_x < 0) + CSSPixels relative_inline_offset = [&]() { + 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; 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()); if (i + 1 < glyphs.size()) { 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; } else { - if (relative_x >= glyph_position) + if (relative_inline_offset >= glyph_position) 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 rect = absolute_rect(); - rect.set_x(rect.x() + pixel_distance_to_first_selected_character); - rect.set_width(pixel_width_of_selection); + switch (orientation()) { + 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; } @@ -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 rect = absolute_rect(); - rect.set_x(rect.x() + pixel_distance_to_first_selected_character); - rect.set_width(pixel_width_of_selection); + switch (orientation()) { + 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; } @@ -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 rect = absolute_rect(); - rect.set_x(rect.x() + pixel_distance_to_first_selected_character); - rect.set_width(pixel_width_of_selection); + switch (orientation()) { + 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; } diff --git a/Userland/Libraries/LibWeb/Painting/PaintableFragment.h b/Userland/Libraries/LibWeb/Painting/PaintableFragment.h index 7f5d31723cf..4f27901a7ab 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintableFragment.h +++ b/Userland/Libraries/LibWeb/Painting/PaintableFragment.h @@ -52,7 +52,7 @@ public: CSSPixels width() const { return m_size.width(); } CSSPixels height() const { return m_size.height(); } - int text_index_at(CSSPixels) const; + int text_index_at(CSSPixelPoint) const; StringView string_view() const;