mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-09-20 00:08:55 +00:00
LibWeb: Ensure hit testing is grapheme aware
Previously, clicking in the middle of a multi-code point grapheme would place the cursor at a code unit index somewhere in the middle of the grapheme. This was not only visually misleading, but the user could then start typing and insert characters in the middle of the cluster. This also made text select pretty wonky. The main issue was that we were treating the glyph index in a glyph run as a code unit index. We must instead map that glyph index back to a code unit index with help from LibGfx (via harfbuzz). The distance computation used here was also a bit off, especially for the last glyph in a glyph run. We essentially want the cursor to end up on whichever edge of the clicked glyph it is closest to. The result of the distance computation limited us to the left edge of the last glyph. Instead, we can use the same edge tracking we use for form- associated elements to handle this for us.
This commit is contained in:
parent
c3f4d32162
commit
1f88e6819a
Notes:
github-actions[bot]
2025-08-22 12:07:55 +00:00
Author: https://github.com/trflynn89
Commit: 1f88e6819a
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/5930
3 changed files with 36 additions and 9 deletions
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
#include <LibWeb/DOM/Range.h>
|
||||
#include <LibWeb/GraphemeEdgeTracker.h>
|
||||
#include <LibWeb/HTML/FormAssociatedElement.h>
|
||||
#include <LibWeb/HTML/HTMLInputElement.h>
|
||||
#include <LibWeb/HTML/HTMLTextAreaElement.h>
|
||||
|
@ -52,18 +53,14 @@ size_t PaintableFragment::index_in_node_for_point(CSSPixelPoint position) const
|
|||
if (relative_inline_offset < 0)
|
||||
return 0;
|
||||
|
||||
auto const& glyphs = m_glyph_run->glyphs();
|
||||
auto smallest_distance = AK::NumericLimits<float>::max();
|
||||
for (size_t i = 0; i < glyphs.size(); ++i) {
|
||||
auto distance_to_position = AK::abs(glyphs[i].position.x() - relative_inline_offset);
|
||||
GraphemeEdgeTracker tracker { relative_inline_offset };
|
||||
|
||||
// The last distance was smaller than this new distance, so we've found the closest glyph.
|
||||
if (distance_to_position > smallest_distance)
|
||||
return m_start_offset + i - 1;
|
||||
smallest_distance = distance_to_position;
|
||||
for (auto const& glyph : m_glyph_run->glyphs()) {
|
||||
if (tracker.update(glyph.length_in_code_units, glyph.glyph_width) == IterationDecision::Break)
|
||||
break;
|
||||
}
|
||||
|
||||
return m_start_offset + m_length_in_code_units - 1;
|
||||
return m_start_offset + tracker.resolve();
|
||||
}
|
||||
|
||||
CSSPixelRect PaintableFragment::range_rect(Paintable::SelectionState selection_state, size_t start_offset_in_code_units, size_t end_offset_in_code_units) const
|
||||
|
|
6
Tests/LibWeb/Text/expected/hit_testing/grapheme.txt
Normal file
6
Tests/LibWeb/Text/expected/hit_testing/grapheme.txt
Normal file
|
@ -0,0 +1,6 @@
|
|||
Click [13, 20]: position=0 text="hello 👩🏼❤️👨🏻👩🏼❤️👨🏻👩🏼❤️👨🏻👩🏼❤️👨🏻"
|
||||
Click [16, 20]: position=1 text="ello 👩🏼❤️👨🏻👩🏼❤️👨🏻👩🏼❤️👨🏻👩🏼❤️👨🏻"
|
||||
Click [52, 20]: position=6 text="👩🏼❤️👨🏻👩🏼❤️👨🏻👩🏼❤️👨🏻👩🏼❤️👨🏻"
|
||||
Click [57, 20]: position=18 text="👩🏼❤️👨🏻👩🏼❤️👨🏻👩🏼❤️👨🏻"
|
||||
Click [85, 20]: position=30 text="👩🏼❤️👨🏻👩🏼❤️👨🏻"
|
||||
Click [90, 20]: position=42 text="👩🏼❤️👨🏻"
|
24
Tests/LibWeb/Text/input/hit_testing/grapheme.html
Normal file
24
Tests/LibWeb/Text/input/hit_testing/grapheme.html
Normal file
|
@ -0,0 +1,24 @@
|
|||
<!doctype html>
|
||||
<script src="../include.js"></script>
|
||||
<textarea id="text">
|
||||
hello 👩🏼❤️👨🏻👩🏼❤️👨🏻👩🏼❤️👨🏻👩🏼❤️👨🏻
|
||||
</textarea>
|
||||
<script>
|
||||
test(() => {
|
||||
const content = text.textContent.trim();
|
||||
|
||||
const click = (x, y) => {
|
||||
internals.click(x, y);
|
||||
|
||||
const remainingText = content.substr(text.selectionStart);
|
||||
println(`Click [${x}, ${y}]: position=${text.selectionStart} text="${remainingText}"`);
|
||||
};
|
||||
|
||||
click(13, 20);
|
||||
click(16, 20);
|
||||
click(52, 20);
|
||||
click(57, 20);
|
||||
click(85, 20);
|
||||
click(90, 20);
|
||||
});
|
||||
</script>
|
Loading…
Add table
Add a link
Reference in a new issue