Fix the offset passed to minikin::GraphemeBreak::isGraphemeBreak (#21706)

The character offset passed to isGraphemeBreak is relative to the beginning
of the string (not relative to the text_start parameter).  This caused bad
results when searching for grapheme breaks beyond the first line of text
(see https://github.com/flutter/flutter/issues/24802).

This PR fixes the offset value.  It also reverts the workaround applied in
https://github.com/flutter/engine/pull/10063, which caused incorrect
calculation of boundaries between graphemes within ligatures.
diff --git a/third_party/txt/src/txt/paragraph_txt.cc b/third_party/txt/src/txt/paragraph_txt.cc
index 780fd61..1e4bcb8 100644
--- a/third_party/txt/src/txt/paragraph_txt.cc
+++ b/third_party/txt/src/txt/paragraph_txt.cc
@@ -194,11 +194,9 @@
 ParagraphTxt::GlyphPosition::GlyphPosition(double x_start,
                                            double x_advance,
                                            size_t code_unit_index,
-                                           size_t code_unit_width,
-                                           size_t cluster)
+                                           size_t code_unit_width)
     : code_units(code_unit_index, code_unit_index + code_unit_width),
-      x_pos(x_start, x_start + x_advance),
-      cluster(cluster) {}
+      x_pos(x_start, x_start + x_advance) {}
 
 void ParagraphTxt::GlyphPosition::Shift(double delta) {
   x_pos.Shift(delta);
@@ -797,7 +795,6 @@
 
     double run_x_offset = 0;
     double justify_x_offset = 0;
-    size_t cluster_unique_id = 0;
     std::vector<PaintRecord> paint_records;
 
     for (auto line_run_it = line_runs.begin(); line_run_it != line_runs.end();
@@ -945,7 +942,7 @@
                  offset < glyph_code_units.end; ++offset) {
               if (minikin::GraphemeBreak::isGraphemeBreak(
                       layout_advances.data(), text_ptr, text_start, text_count,
-                      offset)) {
+                      text_start + offset)) {
                 grapheme_code_unit_counts.push_back(code_unit_count);
                 code_unit_count = 1;
               } else {
@@ -958,10 +955,10 @@
           float grapheme_advance =
               glyph_advance / grapheme_code_unit_counts.size();
 
-          glyph_positions.emplace_back(
-              run_x_offset + glyph_x_offset, grapheme_advance,
-              run.start() + glyph_code_units.start,
-              grapheme_code_unit_counts[0], cluster_unique_id);
+          glyph_positions.emplace_back(run_x_offset + glyph_x_offset,
+                                       grapheme_advance,
+                                       run.start() + glyph_code_units.start,
+                                       grapheme_code_unit_counts[0]);
 
           // Compute positions for the additional graphemes in the ligature.
           for (size_t i = 1; i < grapheme_code_unit_counts.size(); ++i) {
@@ -969,9 +966,8 @@
                 glyph_positions.back().x_pos.end, grapheme_advance,
                 glyph_positions.back().code_units.start +
                     grapheme_code_unit_counts[i - 1],
-                grapheme_code_unit_counts[i], cluster_unique_id);
+                grapheme_code_unit_counts[i]);
           }
-          cluster_unique_id++;
 
           bool at_word_start = false;
           bool at_word_end = false;
@@ -1876,28 +1872,12 @@
 
   size_t x_index;
   const GlyphPosition* gp = nullptr;
-  const GlyphPosition* gp_cluster = nullptr;
-  bool is_cluster_corection = false;
   for (x_index = 0; x_index < line_glyph_position.size(); ++x_index) {
     double glyph_end = (x_index < line_glyph_position.size() - 1)
                            ? line_glyph_position[x_index + 1].x_pos.start
                            : line_glyph_position[x_index].x_pos.end;
-    if (gp_cluster == nullptr ||
-        gp_cluster->cluster != line_glyph_position[x_index].cluster) {
-      gp_cluster = &line_glyph_position[x_index];
-    }
     if (dx < glyph_end) {
-      // Check if the glyph position is part of a cluster. If it is,
-      // we assign the cluster's root GlyphPosition to represent it.
-      if (gp_cluster->cluster == line_glyph_position[x_index].cluster) {
-        gp = gp_cluster;
-        // Detect if the matching GlyphPosition was non-root for the cluster.
-        if (gp_cluster != &line_glyph_position[x_index]) {
-          is_cluster_corection = true;
-        }
-      } else {
-        gp = &line_glyph_position[x_index];
-      }
+      gp = &line_glyph_position[x_index];
       break;
     }
   }
@@ -1918,13 +1898,8 @@
   }
 
   double glyph_center = (gp->x_pos.start + gp->x_pos.end) / 2;
-  // We want to use the root cluster's start when the cluster
-  // was corrected.
-  // TODO(garyq): Detect if the position is in the middle of the cluster
-  // and properly assign the start/end positions.
   if ((direction == TextDirection::ltr && dx < glyph_center) ||
-      (direction == TextDirection::rtl && dx >= glyph_center) ||
-      is_cluster_corection) {
+      (direction == TextDirection::rtl && dx >= glyph_center)) {
     return PositionWithAffinity(gp->code_units.start, DOWNSTREAM);
   } else {
     return PositionWithAffinity(gp->code_units.end, UPSTREAM);
diff --git a/third_party/txt/src/txt/paragraph_txt.h b/third_party/txt/src/txt/paragraph_txt.h
index 4941bb8..0919130 100644
--- a/third_party/txt/src/txt/paragraph_txt.h
+++ b/third_party/txt/src/txt/paragraph_txt.h
@@ -260,17 +260,11 @@
   struct GlyphPosition {
     Range<size_t> code_units;
     Range<double> x_pos;
-    // Tracks the cluster that this glyph position belongs to. For example, in
-    // extended emojis, multiple glyph positions will have the same cluster. The
-    // cluster can be used as a key to distinguish between codepoints that
-    // contribute to the drawing of a single glyph.
-    size_t cluster;
 
     GlyphPosition(double x_start,
                   double x_advance,
                   size_t code_unit_index,
-                  size_t code_unit_width,
-                  size_t cluster);
+                  size_t code_unit_width);
 
     void Shift(double delta);
   };
diff --git a/third_party/txt/tests/paragraph_unittests.cc b/third_party/txt/tests/paragraph_unittests.cc
index 8c806d7..8f94d54 100644
--- a/third_party/txt/tests/paragraph_unittests.cc
+++ b/third_party/txt/tests/paragraph_unittests.cc
@@ -5211,26 +5211,6 @@
   }
   EXPECT_EQ(boxes.size(), 0ull);
 
-  // Ligature style indexing.
-  boxes =
-      paragraph->GetRectsForRange(0, 119, rect_height_style, rect_width_style);
-  for (size_t i = 0; i < boxes.size(); ++i) {
-    GetCanvas()->drawRect(boxes[i].rect, paint);
-  }
-  EXPECT_EQ(boxes.size(), 2ull);
-  EXPECT_FLOAT_EQ(boxes[1].rect.left(), 0);
-  EXPECT_FLOAT_EQ(boxes[1].rect.right(), 334.61475);
-
-  boxes = paragraph->GetRectsForRange(122, 132, rect_height_style,
-                                      rect_width_style);
-  paint.setColor(SK_ColorBLUE);
-  for (size_t i = 0; i < boxes.size(); ++i) {
-    GetCanvas()->drawRect(boxes[i].rect, paint);
-  }
-  EXPECT_EQ(boxes.size(), 1ull);
-  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 357.95996);
-  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 418.79901);
-
   // GetPositionForCoordinates should not return inter-emoji positions.
   boxes = paragraph->GetRectsForRange(
       0, paragraph->GetGlyphPositionAtCoordinate(610, 100).position,
@@ -5241,9 +5221,7 @@
   }
   EXPECT_EQ(boxes.size(), 2ull);
   EXPECT_FLOAT_EQ(boxes[1].rect.left(), 0);
-  // The following is expected to change to a higher value when
-  // rounding up is added to getGlyphPositionForCoordinate.
-  EXPECT_FLOAT_EQ(boxes[1].rect.right(), 560.28516);
+  EXPECT_FLOAT_EQ(boxes[1].rect.right(), 622.53906);
 
   boxes = paragraph->GetRectsForRange(
       0, paragraph->GetGlyphPositionAtCoordinate(580, 100).position,
@@ -5265,13 +5243,45 @@
   }
   EXPECT_EQ(boxes.size(), 2ull);
   EXPECT_FLOAT_EQ(boxes[1].rect.left(), 0);
-  // The following is expected to change to the 560.28516 value when
-  // rounding up is added to getGlyphPositionForCoordinate.
-  EXPECT_FLOAT_EQ(boxes[1].rect.right(), 498.03125);
+  EXPECT_FLOAT_EQ(boxes[1].rect.right(), 560.28516);
 
   ASSERT_TRUE(Snapshot());
 }
 
+TEST_F(ParagraphTest, LINUX_ONLY(LigatureCharacters)) {
+  const char* text = "Office";
+  auto icu_text = icu::UnicodeString::fromUTF8(text);
+  std::u16string u16_text(icu_text.getBuffer(),
+                          icu_text.getBuffer() + icu_text.length());
+
+  txt::ParagraphStyle paragraph_style;
+  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());
+
+  txt::TextStyle text_style;
+  text_style.font_families = std::vector<std::string>(1, "Roboto");
+  text_style.color = SK_ColorBLACK;
+  builder.PushStyle(text_style);
+  builder.AddText(u16_text);
+
+  builder.Pop();
+
+  auto paragraph = BuildParagraph(builder);
+  paragraph->Layout(GetTestCanvasWidth());
+
+  // The "ffi" characters will be combined into one glyph in the Roboto font.
+  // Verify that the graphemes within the glyph have distinct boxes.
+  std::vector<txt::Paragraph::TextBox> boxes =
+      paragraph->GetRectsForRange(1, 2, Paragraph::RectHeightStyle::kTight,
+                                  Paragraph::RectWidthStyle::kTight);
+  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 9.625);
+  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 13.608073);
+
+  boxes = paragraph->GetRectsForRange(2, 4, Paragraph::RectHeightStyle::kTight,
+                                      Paragraph::RectWidthStyle::kTight);
+  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 13.608073);
+  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 21.574219);
+}
+
 TEST_F(ParagraphTest, HyphenBreakParagraph) {
   const char* text =
       "A "
@@ -6409,8 +6419,8 @@
 
   ASSERT_EQ(paragraph->glyph_lines_.size(), 3ull);
   EXPECT_EQ(paragraph->glyph_lines_[0].positions.size(), 7ul);
-  EXPECT_EQ(paragraph->glyph_lines_[1].positions.size(), 12ul);
-  EXPECT_EQ(paragraph->glyph_lines_[2].positions.size(), 7ul);
+  EXPECT_EQ(paragraph->glyph_lines_[1].positions.size(), 7ul);
+  EXPECT_EQ(paragraph->glyph_lines_[2].positions.size(), 3ul);
 
   ASSERT_TRUE(Snapshot());
 }