LibWeb: Use glyph positions & widths instead of Harfbuzz for range rects
We use Harfbuzz to lay out glyphs at their right positions, but we should not do so for substrings as we were doing for selection/range rects: their positioning is dependent on their full context to properly determine kerning and ligatures, for example. Instead, use the stored glyph positions & widths directly to calculate the range rect for a partial fragment selection. This makes the selection rect visually stable - it no longer jitters when expanding the selection character by character.
This commit is contained in:
parent
4a596f1f49
commit
e99ff65a58
6 changed files with 72 additions and 8 deletions
|
|
@ -119,21 +119,32 @@ CSSPixelRect PaintableFragment::range_rect(Paintable::SelectionState selection_s
|
|||
pixel_offset = 0;
|
||||
pixel_width = rect.primary_size_for_orientation(orientation());
|
||||
} else {
|
||||
auto letter_spacing = layout_node().computed_values().letter_spacing().to_float();
|
||||
pixel_offset = CSSPixels { Gfx::measure_text_width(text().substring_view(0, offsets->start), font, letter_spacing) };
|
||||
float offset_accumulator = 0.f;
|
||||
float width_accumulator = 0.f;
|
||||
|
||||
// When start equals end, this is a cursor position.
|
||||
if (offsets->start == offsets->end) {
|
||||
pixel_width = 1;
|
||||
} else {
|
||||
pixel_width = CSSPixels { Gfx::measure_text_width(text().substring_view(offsets->start, offsets->end - offsets->start), font, letter_spacing) };
|
||||
if (m_glyph_run) {
|
||||
size_t code_units_seen = 0;
|
||||
for (auto const& glyph : m_glyph_run->glyphs()) {
|
||||
if (code_units_seen < offsets->start)
|
||||
offset_accumulator += glyph.glyph_width;
|
||||
else if (code_units_seen < offsets->end)
|
||||
width_accumulator += glyph.glyph_width;
|
||||
code_units_seen += glyph.length_in_code_units;
|
||||
}
|
||||
}
|
||||
|
||||
pixel_offset = CSSPixels { offset_accumulator };
|
||||
pixel_width = CSSPixels { width_accumulator };
|
||||
}
|
||||
|
||||
// When start equals end, this is a cursor position.
|
||||
if (offsets->start == offsets->end)
|
||||
pixel_width = 1;
|
||||
|
||||
// Include an additional space at the end if we remembered that this fragment contained trailing whitespace. This
|
||||
// shows the user that at least one whitespace character was present when selecting text, even though we don't store
|
||||
// that whitespace in the glyph run or text fragment.
|
||||
if (m_has_trailing_whitespace && offsets->include_trailing_whitespace && offsets->start != offsets->end)
|
||||
if (offsets->start != offsets->end && m_has_trailing_whitespace && offsets->include_trailing_whitespace)
|
||||
pixel_width += CSSPixels { font.glyph_width(' ') };
|
||||
|
||||
rect.translate_primary_offset_for_orientation(orientation(), pixel_offset);
|
||||
|
|
|
|||
BIN
Tests/LibWeb/Assets/Lato-Bold.ttf
Normal file
BIN
Tests/LibWeb/Assets/Lato-Bold.ttf
Normal file
Binary file not shown.
|
|
@ -1,3 +1,4 @@
|
|||
Lato-Bold.ttf: Copyright (c) 2010-2011 by tyPoland Lukasz Dziedzic (team@latofonts.com) with Reserved Font Name "Lato".
|
||||
NotoEmoji.ttf: Copyright 2013 Google LLC.
|
||||
NotoSansEthiopic.woff2: Copyright 2013 Google LLC.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@
|
|||
; Cookies require HTTP(s) scheme.
|
||||
Text/input/cookie-working.html
|
||||
|
||||
; CSS @font-face url() requires HTTP(s) scheme.
|
||||
Text/input/selection-rect-consistency-with-kerning.html
|
||||
|
||||
; Bug in ladybird - crashes due to AD-HOC fetch implementation in SVGScriptElement (due to opaque file origin).
|
||||
; Works in other browsers when loaded from file://.
|
||||
Text/input/wpt-import/html/syntax/parsing/unclosed-svg-script.html
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
char widths: 22.703125 25.125 27.359375 13.015625 25.65625
|
||||
char sum: 113.859375
|
||||
full width: 113.859375
|
||||
sum == full: true
|
||||
T.right == a.left: true
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<!DOCTYPE html>
|
||||
<script src="include.js"></script>
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: LatoBold;
|
||||
src: url("../../Assets/Lato-Bold.ttf");
|
||||
}
|
||||
p {
|
||||
font: 48px LatoBold;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
<p>Table</p>
|
||||
<script>
|
||||
asyncTest(async done => {
|
||||
await document.fonts.ready;
|
||||
|
||||
const text = document.querySelector("p").firstChild;
|
||||
|
||||
function rangeRect(start, end) {
|
||||
const range = document.createRange();
|
||||
range.setStart(text, start);
|
||||
range.setEnd(text, end);
|
||||
return range.getBoundingClientRect();
|
||||
}
|
||||
|
||||
const full = rangeRect(0, 5);
|
||||
const r01 = rangeRect(0, 1);
|
||||
const r12 = rangeRect(1, 2);
|
||||
const r23 = rangeRect(2, 3);
|
||||
const r34 = rangeRect(3, 4);
|
||||
const r45 = rangeRect(4, 5);
|
||||
|
||||
const charSum = r01.width + r12.width + r23.width + r34.width + r45.width;
|
||||
|
||||
println(`char widths: ${r01.width} ${r12.width} ${r23.width} ${r34.width} ${r45.width}`);
|
||||
println(`char sum: ${charSum}`);
|
||||
println(`full width: ${full.width}`);
|
||||
println(`sum == full: ${charSum === full.width}`);
|
||||
println(`T.right == a.left: ${r01.right === r12.left}`);
|
||||
|
||||
done();
|
||||
});
|
||||
</script>
|
||||
Loading…
Reference in a new issue