From 047f521c4cb3f820ac0ec2ab77408c8263dc33e2 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Tue, 19 Aug 2025 13:33:45 -0400 Subject: [PATCH] LibGfx+LibWeb: Add some extra fields to glyph run data We currently have a mixup in LibWeb between code unit offset and glyph offset during hit testing. These extra fields will allow us to correct this discrepency. --- Libraries/LibGfx/TextLayout.cpp | 27 ++++++++++++++++++- Libraries/LibGfx/TextLayout.h | 9 +++---- .../LibWeb/Layout/InlineFormattingContext.cpp | 5 +++- 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/Libraries/LibGfx/TextLayout.cpp b/Libraries/LibGfx/TextLayout.cpp index 2c0b7416c8e..d036e778c27 100644 --- a/Libraries/LibGfx/TextLayout.cpp +++ b/Libraries/LibGfx/TextLayout.cpp @@ -110,11 +110,36 @@ NonnullRefPtr shape_text(FloatPoint baseline_start, float letter_spaci Vector glyph_run; glyph_run.ensure_capacity(glyph_count); FloatPoint point = baseline_start; + + // We track the code unit length rather than just the code unit offset because LibWeb may later collapse glyph runs. + // Updating the offset of each glyph gets tricky when handling text direction (LTR/RTL). So rather than doing that, + // we just provide the glyph's code unit length and base LibWeb algorithms on that. + // + // A single grapheme may be represented by multiple glyphs, where any of those glyphs are zero-width. We want to + // assign code unit lengths such that each glyph knows the length of the text it respresents. + auto glyph_length_in_code_units = [&](auto index) -> size_t { + auto starting_offset = glyph_info[index].cluster; + + for (size_t i = index + 1; i < glyph_count; ++i) { + if (auto offset = glyph_info[i].cluster; offset != starting_offset) + return offset - starting_offset; + } + + return string.length_in_code_units() - starting_offset; + }; + for (size_t i = 0; i < glyph_count; ++i) { auto position = point - FloatPoint { 0, font.pixel_metrics().ascent } + FloatPoint { positions[i].x_offset, positions[i].y_offset } / text_shaping_resolution; - glyph_run.unchecked_append({ position, glyph_info[i].codepoint }); + + glyph_run.unchecked_append({ + .position = position, + .length_in_code_units = glyph_length_in_code_units(i), + .glyph_width = positions[i].x_advance / text_shaping_resolution, + .glyph_id = glyph_info[i].codepoint, + }); + point += FloatPoint { positions[i].x_advance, positions[i].y_advance } / text_shaping_resolution; // NOTE: The spec says that we "really should not" apply letter-spacing to the trailing edge of a line but diff --git a/Libraries/LibGfx/TextLayout.h b/Libraries/LibGfx/TextLayout.h index f256ac733af..c4c70789f66 100644 --- a/Libraries/LibGfx/TextLayout.h +++ b/Libraries/LibGfx/TextLayout.h @@ -20,12 +20,9 @@ namespace Gfx { struct DrawGlyph { FloatPoint position; - u32 glyph_id; - - void translate_by(FloatPoint const& delta) - { - position.translate_by(delta); - } + size_t length_in_code_units { 0 }; + float glyph_width { 0.0 }; + u32 glyph_id { 0 }; }; typedef struct ShapeFeature { diff --git a/Libraries/LibWeb/Layout/InlineFormattingContext.cpp b/Libraries/LibWeb/Layout/InlineFormattingContext.cpp index d074a7b8d76..4d8bff9cead 100644 --- a/Libraries/LibWeb/Layout/InlineFormattingContext.cpp +++ b/Libraries/LibWeb/Layout/InlineFormattingContext.cpp @@ -395,7 +395,10 @@ void InlineFormattingContext::generate_line_boxes() glyphs.remove(last_glyph_index - 1, remove_item_count); glyphs.append(Gfx::DrawGlyph { .position = last_glyph_position, - .glyph_id = glyph_run->font().glyph_id_for_code_point(ellipsis_codepoint) }); + .length_in_code_units = AK::UnicodeUtils::code_unit_length_for_code_point(ellipsis_codepoint), + .glyph_width = glyph_run->font().glyph_width(ellipsis_codepoint), + .glyph_id = glyph_run->font().glyph_id_for_code_point(ellipsis_codepoint), + }); } } }