Fix a bug in the HTML render's `getClosestGlyphInfo` implementation (#48774)

`closestFragmentAtOffset` takes a horizontal offset from the start of the line, not the start of the paragraph. 

[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
diff --git a/lib/web_ui/lib/src/engine/text/layout_service.dart b/lib/web_ui/lib/src/engine/text/layout_service.dart
index cbaf045..3273c85 100644
--- a/lib/web_ui/lib/src/engine/text/layout_service.dart
+++ b/lib/web_ui/lib/src/engine/text/layout_service.dart
@@ -420,8 +420,11 @@
   }
 
   ui.GlyphInfo? getClosestGlyphInfo(ui.Offset offset) {
-    final LayoutFragment? fragment = _findLineForY(offset.dy)
-      ?.closestFragmentAtOffset(offset.dx);
+    final ParagraphLine? line = _findLineForY(offset.dy);
+    if (line == null) {
+      return null;
+    }
+    final LayoutFragment? fragment = line.closestFragmentAtOffset(offset.dx - line.left);
     if (fragment == null) {
       return null;
     }
@@ -431,8 +434,8 @@
                                              || fragment.line.left + fragment.line.width <= dx
                                              || switch (fragment.textDirection!) {
                                                // If dx is closer to the trailing edge, no need to check other fragments.
-                                               ui.TextDirection.ltr => dx >= (fragment.left + fragment.right) / 2,
-                                               ui.TextDirection.rtl => dx <= (fragment.left + fragment.right) / 2,
+                                               ui.TextDirection.ltr => dx >= line.left + (fragment.left + fragment.right) / 2,
+                                               ui.TextDirection.rtl => dx <= line.left + (fragment.left + fragment.right) / 2,
                                              };
     final ui.GlyphInfo candidate1 = fragment.getClosestCharacterBox(dx);
     if (closestGraphemeStartInFragment) {
diff --git a/lib/web_ui/lib/src/engine/text/paragraph.dart b/lib/web_ui/lib/src/engine/text/paragraph.dart
index c14bbe0..49a34f1 100644
--- a/lib/web_ui/lib/src/engine/text/paragraph.dart
+++ b/lib/web_ui/lib/src/engine/text/paragraph.dart
@@ -380,7 +380,7 @@
 
       final double? minDistance = closestFragment?.distance;
       if (minDistance == null || minDistance > distance) {
-          closestFragment = (fragment: fragment, distance: distance);
+        closestFragment = (fragment: fragment, distance: distance);
       }
     }
     return closestFragment?.fragment;
diff --git a/lib/web_ui/test/html/text_test.dart b/lib/web_ui/test/html/text_test.dart
index 7d43031..d7db0eb 100644
--- a/lib/web_ui/test/html/text_test.dart
+++ b/lib/web_ui/test/html/text_test.dart
@@ -155,6 +155,29 @@
     expect(bottomRight?.writingDirection, TextDirection.ltr);
   });
 
+  test('Basic glyph metrics - hit test - center aligned text in separate fragments', () {
+    const double fontSize = 10.0;
+    final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
+      fontSize: fontSize,
+      textAlign: TextAlign.center,
+      fontFamily: 'FlutterTest',
+    ))..addText('12345\n')
+      ..addText('1')
+      ..addText('2')
+      ..addText('3');
+    final Paragraph paragraph = builder.build();
+    paragraph.layout(const ParagraphConstraints(width: 50));
+
+    final GlyphInfo? bottomCenter = paragraph.getClosestGlyphInfoForOffset(const Offset(25.0, 99.0));
+    final GlyphInfo? expected = paragraph.getGlyphInfoAt(7);
+    expect(bottomCenter, equals(expected));
+    expect(bottomCenter, isNot(paragraph.getGlyphInfoAt(8)));
+
+    expect(bottomCenter?.graphemeClusterLayoutBounds, const Rect.fromLTWH(20, 10, 10, 10));
+    expect(bottomCenter?.graphemeClusterCodeUnitRange, const TextRange(start: 7, end: 8));
+    expect(bottomCenter?.writingDirection, TextDirection.ltr);
+  });
+
   test('Glyph metrics with grapheme split into different runs', () {
     const double fontSize = 10;
     final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(