Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 3c76b90

Browse files
authored
Track clusters and return cluster boundaries in getGlyphPositionForCoordinates (emoji fix) (#10063)
1 parent e8fb641 commit 3c76b90

File tree

3 files changed

+154
-9
lines changed

3 files changed

+154
-9
lines changed

third_party/txt/src/txt/paragraph_txt.cc

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -193,9 +193,11 @@ static const float kDoubleDecorationSpacing = 3.0f;
193193
ParagraphTxt::GlyphPosition::GlyphPosition(double x_start,
194194
double x_advance,
195195
size_t code_unit_index,
196-
size_t code_unit_width)
196+
size_t code_unit_width,
197+
size_t cluster)
197198
: code_units(code_unit_index, code_unit_index + code_unit_width),
198-
x_pos(x_start, x_start + x_advance) {}
199+
x_pos(x_start, x_start + x_advance),
200+
cluster(cluster) {}
199201

200202
void ParagraphTxt::GlyphPosition::Shift(double delta) {
201203
x_pos.Shift(delta);
@@ -881,13 +883,11 @@ void ParagraphTxt::Layout(double width) {
881883
builder.allocRunPos(font, glyph_blob.end - glyph_blob.start);
882884

883885
double justify_x_offset_delta = 0;
884-
885886
for (size_t glyph_index = glyph_blob.start;
886887
glyph_index < glyph_blob.end;) {
887888
size_t cluster_start_glyph_index = glyph_index;
888889
uint32_t cluster = layout.getGlyphCluster(cluster_start_glyph_index);
889890
double glyph_x_offset;
890-
891891
// Add all the glyphs in this cluster to the text blob.
892892
do {
893893
size_t blob_index = glyph_index - glyph_blob.start;
@@ -946,15 +946,15 @@ void ParagraphTxt::Layout(double width) {
946946
glyph_positions.emplace_back(run_x_offset + glyph_x_offset,
947947
grapheme_advance,
948948
run.start() + glyph_code_units.start,
949-
grapheme_code_unit_counts[0]);
949+
grapheme_code_unit_counts[0], cluster);
950950

951951
// Compute positions for the additional graphemes in the ligature.
952952
for (size_t i = 1; i < grapheme_code_unit_counts.size(); ++i) {
953953
glyph_positions.emplace_back(
954954
glyph_positions.back().x_pos.end, grapheme_advance,
955955
glyph_positions.back().code_units.start +
956956
grapheme_code_unit_counts[i - 1],
957-
grapheme_code_unit_counts[i]);
957+
grapheme_code_unit_counts[i], cluster);
958958
}
959959

960960
bool at_word_start = false;
@@ -1772,12 +1772,28 @@ Paragraph::PositionWithAffinity ParagraphTxt::GetGlyphPositionAtCoordinate(
17721772

17731773
size_t x_index;
17741774
const GlyphPosition* gp = nullptr;
1775+
const GlyphPosition* gp_cluster = nullptr;
1776+
bool is_cluster_corection = false;
17751777
for (x_index = 0; x_index < line_glyph_position.size(); ++x_index) {
17761778
double glyph_end = (x_index < line_glyph_position.size() - 1)
17771779
? line_glyph_position[x_index + 1].x_pos.start
17781780
: line_glyph_position[x_index].x_pos.end;
1781+
if (gp_cluster == nullptr ||
1782+
gp_cluster->cluster != line_glyph_position[x_index].cluster) {
1783+
gp_cluster = &line_glyph_position[x_index];
1784+
}
17791785
if (dx < glyph_end) {
1780-
gp = &line_glyph_position[x_index];
1786+
// Check if the glyph position is part of a cluster. If it is,
1787+
// we assign the cluster's root GlyphPosition to represent it.
1788+
if (gp_cluster->cluster == line_glyph_position[x_index].cluster) {
1789+
gp = gp_cluster;
1790+
// Detect if the matching GlyphPosition was non-root for the cluster.
1791+
if (gp_cluster != &line_glyph_position[x_index]) {
1792+
is_cluster_corection = true;
1793+
}
1794+
} else {
1795+
gp = &line_glyph_position[x_index];
1796+
}
17811797
break;
17821798
}
17831799
}
@@ -1798,8 +1814,13 @@ Paragraph::PositionWithAffinity ParagraphTxt::GetGlyphPositionAtCoordinate(
17981814
}
17991815

18001816
double glyph_center = (gp->x_pos.start + gp->x_pos.end) / 2;
1817+
// We want to use the root cluster's start when the cluster
1818+
// was corrected.
1819+
// TODO(garyq): Detect if the position is in the middle of the cluster
1820+
// and properly assign the start/end positions.
18011821
if ((direction == TextDirection::ltr && dx < glyph_center) ||
1802-
(direction == TextDirection::rtl && dx >= glyph_center)) {
1822+
(direction == TextDirection::rtl && dx >= glyph_center) ||
1823+
is_cluster_corection) {
18031824
return PositionWithAffinity(gp->code_units.start, DOWNSTREAM);
18041825
} else {
18051826
return PositionWithAffinity(gp->code_units.end, UPSTREAM);

third_party/txt/src/txt/paragraph_txt.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ class ParagraphTxt : public Paragraph {
139139
FRIEND_TEST(ParagraphTest, KernScaleParagraph);
140140
FRIEND_TEST_WINDOWS_DISABLED(ParagraphTest, NewlineParagraph);
141141
FRIEND_TEST_WINDOWS_DISABLED(ParagraphTest, EmojiParagraph);
142+
FRIEND_TEST_WINDOWS_DISABLED(ParagraphTest, EmojiMultiLineRectsParagraph);
142143
FRIEND_TEST(ParagraphTest, HyphenBreakParagraph);
143144
FRIEND_TEST(ParagraphTest, RepeatLayoutParagraph);
144145
FRIEND_TEST(ParagraphTest, Ellipsize);
@@ -262,11 +263,17 @@ class ParagraphTxt : public Paragraph {
262263
struct GlyphPosition {
263264
Range<size_t> code_units;
264265
Range<double> x_pos;
266+
// Tracks the cluster that this glyph position belongs to. For example, in
267+
// extended emojis, multiple glyph positions will have the same cluster. The
268+
// cluster can be used as a key to distinguish between codepoints that
269+
// contribute to the drawing of a single glyph.
270+
size_t cluster;
265271

266272
GlyphPosition(double x_start,
267273
double x_advance,
268274
size_t code_unit_index,
269-
size_t code_unit_width);
275+
size_t code_unit_width,
276+
size_t cluster);
270277

271278
void Shift(double delta);
272279
};

third_party/txt/tests/paragraph_unittests.cc

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4042,6 +4042,123 @@ TEST_F(ParagraphTest, DISABLE_ON_WINDOWS(EmojiParagraph)) {
40424042
EXPECT_EQ(paragraph->records_[7].line(), 7ull);
40434043
}
40444044

4045+
TEST_F(ParagraphTest, DISABLE_ON_WINDOWS(EmojiMultiLineRectsParagraph)) {
4046+
// clang-format off
4047+
const char* text =
4048+
"👩‍👩‍👦👩‍👩‍👧‍👧🇺🇸👩‍👩‍👦👩‍👩‍👧‍👧i🇺🇸👩‍👩‍👦👩‍👩‍👧‍👧🇺🇸👩‍👩‍👦👩‍👩‍👧‍👧🇺🇸"
4049+
"👩‍👩‍👦👩‍👩‍👧‍👧🇺🇸👩‍👩‍👦👩‍👩‍👧‍👧🇺🇸👩‍👩‍👦👩‍👩‍👧‍👧🇺🇸👩‍👩‍👦👩‍👩‍👧‍👧🇺🇸"
4050+
"👩‍👩‍👦👩‍👩‍👧‍👧🇺🇸👩‍👩‍👦👩‍👩‍👧‍👧🇺🇸👩‍👩‍👦👩‍👩‍👧‍👧🇺🇸👩‍👩‍👦👩‍👩‍👧‍👧🇺🇸"
4051+
"👩‍👩‍👦👩‍👩‍👧‍👧🇺🇸👩‍👩‍👦👩‍👩‍👧‍👧🇺🇸👩‍👩‍👦👩‍👩‍👧‍👧🇺🇸👩‍👩‍👦👩‍👩‍👧‍👧🇺🇸"
4052+
"❄🍕🍔🍟🥝🍱🕶🎩🏈⚽🚴‍♀️🎻🎼🎹🚨🚎🚐⚓🛳🚀🚁🏪🏢🖱⏰📱💾💉📉🛏🔑🔓"
4053+
"📁🗓📊❤💯🚫🔻♠♣🕓❗🏳🏁🏳️‍🌈🇮🇹🇱🇷🇺🇸🇬🇧🇨🇳🇧🇴";
4054+
// clang-format on
4055+
auto icu_text = icu::UnicodeString::fromUTF8(text);
4056+
std::u16string u16_text(icu_text.getBuffer(),
4057+
icu_text.getBuffer() + icu_text.length());
4058+
4059+
txt::ParagraphStyle paragraph_style;
4060+
4061+
txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());
4062+
4063+
txt::TextStyle text_style;
4064+
text_style.color = SK_ColorBLACK;
4065+
text_style.font_families = std::vector<std::string>(1, "Noto Color Emoji");
4066+
text_style.font_size = 50;
4067+
builder.PushStyle(text_style);
4068+
builder.AddText(u16_text);
4069+
4070+
builder.Pop();
4071+
4072+
auto paragraph = BuildParagraph(builder);
4073+
paragraph->Layout(GetTestCanvasWidth() - 300);
4074+
4075+
paragraph->Paint(GetCanvas(), 0, 0);
4076+
4077+
for (size_t i = 0; i < u16_text.length(); i++) {
4078+
ASSERT_EQ(paragraph->text_[i], u16_text[i]);
4079+
}
4080+
4081+
ASSERT_EQ(paragraph->records_.size(), 10ull);
4082+
4083+
SkPaint paint;
4084+
paint.setStyle(SkPaint::kStroke_Style);
4085+
paint.setAntiAlias(true);
4086+
paint.setStrokeWidth(1);
4087+
4088+
// Tests for GetRectsForRange()
4089+
Paragraph::RectHeightStyle rect_height_style =
4090+
Paragraph::RectHeightStyle::kTight;
4091+
Paragraph::RectWidthStyle rect_width_style =
4092+
Paragraph::RectWidthStyle::kTight;
4093+
paint.setColor(SK_ColorRED);
4094+
std::vector<txt::Paragraph::TextBox> boxes =
4095+
paragraph->GetRectsForRange(0, 0, rect_height_style, rect_width_style);
4096+
for (size_t i = 0; i < boxes.size(); ++i) {
4097+
GetCanvas()->drawRect(boxes[i].rect, paint);
4098+
}
4099+
EXPECT_EQ(boxes.size(), 0ull);
4100+
4101+
// Ligature style indexing.
4102+
boxes =
4103+
paragraph->GetRectsForRange(0, 119, rect_height_style, rect_width_style);
4104+
for (size_t i = 0; i < boxes.size(); ++i) {
4105+
GetCanvas()->drawRect(boxes[i].rect, paint);
4106+
}
4107+
EXPECT_EQ(boxes.size(), 2ull);
4108+
EXPECT_FLOAT_EQ(boxes[1].rect.left(), 0);
4109+
EXPECT_FLOAT_EQ(boxes[1].rect.right(), 334.61475);
4110+
4111+
boxes = paragraph->GetRectsForRange(122, 132, rect_height_style,
4112+
rect_width_style);
4113+
paint.setColor(SK_ColorBLUE);
4114+
for (size_t i = 0; i < boxes.size(); ++i) {
4115+
GetCanvas()->drawRect(boxes[i].rect, paint);
4116+
}
4117+
EXPECT_EQ(boxes.size(), 1ull);
4118+
EXPECT_FLOAT_EQ(boxes[0].rect.left(), 357.95996);
4119+
EXPECT_FLOAT_EQ(boxes[0].rect.right(), 418.79901);
4120+
4121+
// GetPositionForCoordinates should not return inter-emoji positions.
4122+
boxes = paragraph->GetRectsForRange(
4123+
0, paragraph->GetGlyphPositionAtCoordinate(610, 100).position,
4124+
rect_height_style, rect_width_style);
4125+
paint.setColor(SK_ColorGREEN);
4126+
for (size_t i = 0; i < boxes.size(); ++i) {
4127+
GetCanvas()->drawRect(boxes[i].rect, paint);
4128+
}
4129+
EXPECT_EQ(boxes.size(), 2ull);
4130+
EXPECT_FLOAT_EQ(boxes[1].rect.left(), 0);
4131+
// The following is expected to change to a higher value when
4132+
// rounding up is added to getGlyphPositionForCoordinate.
4133+
EXPECT_FLOAT_EQ(boxes[1].rect.right(), 560.28516);
4134+
4135+
boxes = paragraph->GetRectsForRange(
4136+
0, paragraph->GetGlyphPositionAtCoordinate(580, 100).position,
4137+
rect_height_style, rect_width_style);
4138+
paint.setColor(SK_ColorGREEN);
4139+
for (size_t i = 0; i < boxes.size(); ++i) {
4140+
GetCanvas()->drawRect(boxes[i].rect, paint);
4141+
}
4142+
EXPECT_EQ(boxes.size(), 2ull);
4143+
EXPECT_FLOAT_EQ(boxes[1].rect.left(), 0);
4144+
EXPECT_FLOAT_EQ(boxes[1].rect.right(), 560.28516);
4145+
4146+
boxes = paragraph->GetRectsForRange(
4147+
0, paragraph->GetGlyphPositionAtCoordinate(560, 100).position,
4148+
rect_height_style, rect_width_style);
4149+
paint.setColor(SK_ColorGREEN);
4150+
for (size_t i = 0; i < boxes.size(); ++i) {
4151+
GetCanvas()->drawRect(boxes[i].rect, paint);
4152+
}
4153+
EXPECT_EQ(boxes.size(), 2ull);
4154+
EXPECT_FLOAT_EQ(boxes[1].rect.left(), 0);
4155+
// The following is expected to change to the 560.28516 value when
4156+
// rounding up is added to getGlyphPositionForCoordinate.
4157+
EXPECT_FLOAT_EQ(boxes[1].rect.right(), 498.03125);
4158+
4159+
ASSERT_TRUE(Snapshot());
4160+
}
4161+
40454162
TEST_F(ParagraphTest, HyphenBreakParagraph) {
40464163
const char* text =
40474164
"A "

0 commit comments

Comments
 (0)