mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-24 18:02:20 +00:00
LibWeb: Treat DOM::Range
offsets as UTF-16 code unit offsets
We generated `PaintableFragment`s with a start and length represented in UTF-8 byte offsets, but failed to consider that the offsets in a `DOM::Range` are actually expressed in UTF-16 code units. This is a bit of a mess: almost all web specs use UTF-16 code units as the unit for indexing into text nodes, but we almost exclusively use UTF-8 in our code base. Arguably the best thing would for us to use UTF-16 everywhere as well: it prevents these mismatches in our implementations for the price of a bit more memory usage - and even that could potentially be optimized for. But for now, try to do the correct thing and lazily allocate UTF-16 data in a `PaintableFragment` whenever we need to index into it or if we're asked to determine the code unit offset of a pixel position.
This commit is contained in:
parent
dbbdf2cebc
commit
3df83dade8
Notes:
github-actions[bot]
2025-06-13 13:09:49 +00:00
Author: https://github.com/gmta
Commit: 3df83dade8
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/5067
Reviewed-by: https://github.com/tcl3
Reviewed-by: https://github.com/trflynn89
6 changed files with 110 additions and 36 deletions
|
@ -670,7 +670,14 @@ void paint_cursor_if_needed(PaintContext& context, TextPaintable const& paintabl
|
|||
return;
|
||||
|
||||
// NOTE: This checks if the cursor is before the start or after the end of the fragment. If it is at the end, after all text, it should still be painted.
|
||||
if (cursor_position->offset() < (unsigned)fragment.start() || cursor_position->offset() > (unsigned)(fragment.start() + fragment.length()))
|
||||
size_t cursor_position_byte_offset = 0;
|
||||
if (cursor_position->offset() == fragment.utf16_view().length_in_code_units()) {
|
||||
cursor_position_byte_offset = fragment.utf8_view().byte_length();
|
||||
} else {
|
||||
auto cursor_position_code_point_offset = fragment.utf16_view().code_point_offset_of(cursor_position->offset());
|
||||
cursor_position_byte_offset = fragment.utf8_view().byte_offset_of(cursor_position_code_point_offset);
|
||||
}
|
||||
if (cursor_position_byte_offset < fragment.start() || cursor_position_byte_offset > (fragment.start() + fragment.length()))
|
||||
return;
|
||||
|
||||
auto active_element = document.active_element();
|
||||
|
@ -687,10 +694,12 @@ void paint_cursor_if_needed(PaintContext& context, TextPaintable const& paintabl
|
|||
|
||||
auto fragment_rect = fragment.absolute_rect();
|
||||
|
||||
auto text = fragment.string_view();
|
||||
auto const& font = fragment.glyph_run() ? fragment.glyph_run()->font() : fragment.layout_node().first_available_font();
|
||||
auto utf8_text = fragment.utf8_view();
|
||||
auto cursor_offset = font.width(utf8_text.substring_view(fragment.start(), cursor_position_byte_offset - fragment.start()));
|
||||
|
||||
CSSPixelRect cursor_rect {
|
||||
fragment_rect.x() + CSSPixels::nearest_value_for(font.width(text.substring_view(0, document.cursor_position()->offset() - fragment.start()))),
|
||||
fragment_rect.x() + CSSPixels::nearest_value_for(cursor_offset),
|
||||
fragment_rect.top(),
|
||||
1,
|
||||
fragment_rect.height()
|
||||
|
@ -1182,7 +1191,7 @@ TraversalDecision PaintableWithLines::hit_test(CSSPixelPoint position, HitTestTy
|
|||
if (fragment_absolute_rect.contains(transformed_position_adjusted_by_scroll_offset)) {
|
||||
if (fragment.paintable().hit_test(transformed_position_adjusted_by_scroll_offset, type, callback) == TraversalDecision::Break)
|
||||
return TraversalDecision::Break;
|
||||
HitTestResult hit_test_result { const_cast<Paintable&>(fragment.paintable()), fragment.text_index_at(transformed_position_adjusted_by_scroll_offset), 0, 0 };
|
||||
HitTestResult hit_test_result { const_cast<Paintable&>(fragment.paintable()), fragment.index_in_node_for_point(transformed_position_adjusted_by_scroll_offset), 0, 0 };
|
||||
if (callback(hit_test_result) == TraversalDecision::Break)
|
||||
return TraversalDecision::Break;
|
||||
} else if (type == HitTestType::TextCursor) {
|
||||
|
@ -1208,7 +1217,7 @@ TraversalDecision PaintableWithLines::hit_test(CSSPixelPoint position, HitTestTy
|
|||
if (fragment_absolute_rect.bottom() - 1 <= transformed_position_adjusted_by_scroll_offset.y()) { // fully below the fragment
|
||||
HitTestResult hit_test_result {
|
||||
.paintable = const_cast<Paintable&>(fragment.paintable()),
|
||||
.index_in_node = fragment.start() + fragment.length(),
|
||||
.index_in_node = fragment.index_in_node_for_byte_offset(fragment.start() + fragment.length()),
|
||||
.vertical_distance = transformed_position_adjusted_by_scroll_offset.y() - fragment_absolute_rect.bottom(),
|
||||
};
|
||||
if (callback(hit_test_result) == TraversalDecision::Break)
|
||||
|
@ -1217,7 +1226,7 @@ TraversalDecision PaintableWithLines::hit_test(CSSPixelPoint position, HitTestTy
|
|||
if (transformed_position_adjusted_by_scroll_offset.x() < fragment_absolute_rect.left()) {
|
||||
HitTestResult hit_test_result {
|
||||
.paintable = const_cast<Paintable&>(fragment.paintable()),
|
||||
.index_in_node = fragment.start(),
|
||||
.index_in_node = fragment.index_in_node_for_byte_offset(fragment.start()),
|
||||
.vertical_distance = 0,
|
||||
.horizontal_distance = fragment_absolute_rect.left() - transformed_position_adjusted_by_scroll_offset.x(),
|
||||
};
|
||||
|
@ -1226,7 +1235,7 @@ TraversalDecision PaintableWithLines::hit_test(CSSPixelPoint position, HitTestTy
|
|||
} else if (transformed_position_adjusted_by_scroll_offset.x() > fragment_absolute_rect.right()) {
|
||||
HitTestResult hit_test_result {
|
||||
.paintable = const_cast<Paintable&>(fragment.paintable()),
|
||||
.index_in_node = fragment.start() + fragment.length(),
|
||||
.index_in_node = fragment.index_in_node_for_byte_offset(fragment.start() + fragment.length()),
|
||||
.vertical_distance = 0,
|
||||
.horizontal_distance = transformed_position_adjusted_by_scroll_offset.x() - fragment_absolute_rect.right(),
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue