Respect grapheme clusters when wrapping text
Bug: skia:10075
Change-Id: I52fad5db0944e74c780c1dbfa0c8e6eb7fa42cf1
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/278468
Reviewed-by: Ben Wagner <bungeman@google.com>
Commit-Queue: Julia Lavrova <jlavrova@google.com>
diff --git a/modules/skparagraph/src/ParagraphImpl.cpp b/modules/skparagraph/src/ParagraphImpl.cpp
index 0598557..61f283e 100644
--- a/modules/skparagraph/src/ParagraphImpl.cpp
+++ b/modules/skparagraph/src/ParagraphImpl.cpp
@@ -271,6 +271,9 @@
auto& cluster = fClusters.emplace_back(this, runIndex, glyphStart, glyphEnd, text,
width, height);
cluster.setIsWhiteSpaces();
+ if (fGraphemes.find(cluster.fTextRange.end) != nullptr) {
+ cluster.setBreakType(Cluster::BreakType::GraphemeBreak);
+ }
});
}
@@ -288,6 +291,8 @@
return;
}
+ // Mark all soft line breaks
+ // Remove soft line breaks that are not on grapheme cluster edge
Cluster* current = fClusters.begin();
while (!breaker.eof() && current < fClusters.end()) {
size_t currentPos = breaker.next();
@@ -295,9 +300,15 @@
if (current->textRange().end > currentPos) {
break;
} else if (current->textRange().end == currentPos) {
- current->setBreakType(breaker.status() == UBRK_LINE_HARD
- ? Cluster::BreakType::HardLineBreak
- : Cluster::BreakType::SoftLineBreak);
+ if (breaker.status() == UBRK_LINE_HARD) {
+ // Hard line break stronger than anything
+ current->setBreakType(Cluster::BreakType::HardLineBreak);
+ } else if (current->isGraphemeBreak()) {
+ // Only allow soft line break if it's grapheme break
+ current->setBreakType(Cluster::BreakType::SoftLineBreak);
+ } else {
+ // Leave it as is (either it's no break or a placeholder)
+ }
++current;
break;
}
@@ -305,7 +316,6 @@
}
}
-
// Walk through all the clusters in the direction of shaped text
// (we have to walk through the styles in the same order, too)
SkScalar shift = 0;
diff --git a/modules/skparagraph/src/ParagraphImpl.h b/modules/skparagraph/src/ParagraphImpl.h
index 51007cf..b692173 100644
--- a/modules/skparagraph/src/ParagraphImpl.h
+++ b/modules/skparagraph/src/ParagraphImpl.h
@@ -142,6 +142,7 @@
const ParagraphStyle& paragraphStyle() const { return fParagraphStyle; }
SkSpan<Cluster> clusters() { return SkSpan<Cluster>(fClusters.begin(), fClusters.size()); }
sk_sp<FontCollection> fontCollection() const { return fFontCollection; }
+ const SkTHashSet<size_t>& graphemes() const { return fGraphemes; }
void formatLines(SkScalar maxWidth);
bool strutEnabled() const { return paragraphStyle().getStrutStyle().getStrutEnabled(); }
diff --git a/modules/skparagraph/src/Run.h b/modules/skparagraph/src/Run.h
index ce91c19d..341aac2 100644
--- a/modules/skparagraph/src/Run.h
+++ b/modules/skparagraph/src/Run.h
@@ -233,6 +233,7 @@
WordBreakWithHyphen,
SoftLineBreak, // calculated for all clusters (UBRK_LINE)
HardLineBreak, // calculated for all clusters (UBRK_LINE)
+ GraphemeBreak,
};
Cluster()
@@ -279,6 +280,7 @@
}
bool isHardBreak() const { return fBreakType == HardLineBreak; }
bool isSoftBreak() const { return fBreakType == SoftLineBreak; }
+ bool isGraphemeBreak() const { return fBreakType == GraphemeBreak; }
size_t startPos() const { return fStart; }
size_t endPos() const { return fEnd; }
SkScalar width() const { return fWidth; }
diff --git a/samplecode/SampleParagraph.cpp b/samplecode/SampleParagraph.cpp
index fc04bb2..60d3494 100644
--- a/samplecode/SampleParagraph.cpp
+++ b/samplecode/SampleParagraph.cpp
@@ -2491,8 +2491,8 @@
SkString name() override { return SkString("Paragraph37"); }
void onDrawContent(SkCanvas* canvas) override {
-
- const char* text = "ছোৈূোঌ";
+ const char* text = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaয়ৠঝোণ৺ঢ়মৈবৗৗঘথফড়৭২খসঢ়ৃঢ়ঁ৷থডঈঽলবনদ২ৢৃঀজঝ৩ঠ৪৫৯০ঌয়্মওৗ৲গখদ৹ঈ৴৹ঢ়ৄএৡফণহলঈ৲থজোৱে ঀকৰঀষজঝঃাখশঽএমংি";
+ //"ৎৣ়ৎঽতঃ৳্ৱব৴ৣঈ৷ূঁঢঢ়শটডৎ৵৵ৰৃ্দংঊাথৗদঊউদ৯ঐৃধা৬হওধি়৭ঽম৯স০ঢফৈঢ়কষঁছফীআে৶ৰ৶ঌৌঊ্ঊঝএঀঃদঞ৮তব৬ৄঊঙঢ়ৡগ৶৹৹ঌড়ঘৄ৷লপ১ভড়৶েঢ়৯ৎকনংট২ংএঢৌৌঐনো০টঽুৠগআ৷৭৩৬তো৻ঈ০ূসষঅঝআমণঔা১ণৈো৵চঽ৩বমৎঙঘ২ঠৠৈী৫তঌণচ৲ঔী৮ঘৰঔ";
canvas->drawColor(SK_ColorWHITE);
auto fontCollection = sk_make_sp<FontCollection>();
@@ -2510,6 +2510,27 @@
auto paragraph = builder.Build();
auto w = width() / 2;
paragraph->layout(w);
+ auto impl = static_cast<ParagraphImpl*>(paragraph.get());
+
+ auto clusters = impl->clusters();
+ size_t c = 0;
+ SkDebugf("clusters\n");
+ for (auto& cluster: clusters) {
+ SkDebugf(""
+ "%d: [%d:%d) %s\n", c++,
+ cluster.textRange().start, cluster.textRange().end,
+ cluster.isSoftBreak() ? "soft" :
+ cluster.isHardBreak() ? "hard" :
+ cluster.isWhitespaces() ? "spaces" : ""
+ );
+ }
+ auto lines = impl->lines();
+ size_t i = 0;
+ SkDebugf("lines\n");
+ for (auto& line : lines) {
+ SkDebugf("%d: [%d:%d)\n", i++, line.trimmedText().start, line.trimmedText().end);
+ }
+
paragraph->paint(canvas, 0, 0);
}
@@ -2517,7 +2538,6 @@
typedef Sample INHERITED;
};
-//"\U0001f469\u200D\U0001f469\u200D\U0001f466\U0001f469\u200D\U0001f469\u200D\U0001f467\u200D\U0001f467\U0001f1fa\U0001f1f8"
//////////////////////////////////////////////////////////////////////////////
DEF_SAMPLE(return new ParagraphView1();)
DEF_SAMPLE(return new ParagraphView2();)